linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4]
@ 2017-05-22 15:50 David Howells
  2017-05-22 15:51 ` [PATCH 01/23] Provide a function to create a NUL-terminated string from unterminated data " David Howells
                   ` (26 more replies)
  0 siblings, 27 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:50 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel


Here are a set of patches to create a superblock configuration context
prior to setting up a new mount, populating it with the parsed
options/binary data, creating the superblock and then effecting the mount.

This allows namespaces and other information to be conveyed through the
mount procedure.  It also allows extra error information to be returned
(so many things can go wrong during a mount that a small integer isn't
really sufficient to convey the issue).

This also allows Miklós Szeredi's idea of doing:

	fd = fsopen("nfs");
	write(fd, "option=val", ...);
	fsmount(fd, "/mnt");

that he presented at LSF-2017 to be implemented (see the relevant patches
in the series), to which I can add:

	read(fd, error_buffer, ...);

to read back any error message.  I didn't use netlink as that would make it
depend on CONFIG_NET and would introduce network namespacing issues.

I've implemented mount context handling for procfs and nfs.

Significant changes:

 ver #4:

 (*) Split the sb_config patch up somewhat.

 (*) Made the supplementary error string facility something attached to the
     task_struct rather than the sb_config so that error messages can be
     obtained from NFS doing a mount-root-and-pathwalk inside the
     nfs_get_tree() operation.

     Further, made this managed and read by prctl rather than through the
     mount fd so that it's more generally available.

 ver #3:

 (*) Rebased on 4.12-rc1.

 (*) Split the NFS patch up somewhat.

 ver #2:

 (*) Removed the ->fill_super() from sb_config_operations and passed it in
     directly to functions that want to call it.  NFS now calls
     nfs_fill_super() directly rather than jumping through a pointer to it
     since there's only the one option at the moment.

 (*) Removed ->mnt_ns and ->sb from sb_config and moved ->pid_ns into
     proc_sb_config.

 (*) Renamed create_super -> get_tree.

 (*) Renamed struct mount_context to struct sb_config and amended various
     variable names.

 (*) sys_fsmount() acquired AT_* flags and MS_* flags (for MNT_* flags)
     arguments.

 ver #1:

 (*) Split the sb_config stuff out into its own header.

 (*) Support non-context aware filesystems through a special set of
     sb_config operations.

 (*) Stored the created superblock and root dentry into the sb_config after
     creation rather than directly into a vfsmount.  This allows some
     arguments to be removed to various NFS functions.

 (*) Added an explicit superblock-creation step.  This allows a created
     superblock to then be mounted multiple times.

 (*) Added a flag to say that the sb_config is degraded and cannot have
     another go at having a superblock creation whilst getting rid of the
     one that says it's already mounted.

Further developments:

 (*) Implement sb reconfiguration (for now it returns ENOANO).

 (*) Implement mount context support in more filesystems, ext4 being next
     on my list.

 (*) Move the walk-from-root stuff that nfs has to generic code so that you
     can do something akin to:

	mount /dev/sda1:/foo/bar /mnt

     See nfs_follow_remote_path() and mount_subtree().  This is slightly
     tricky in NFS as we have to prevent referral loops.

 (*) Work out how to get at the error message incurred by submounts
     encountered during nfs_follow_remote_path().

     Should the error message be moved to task_struct and made more
     general, perhaps retrieved with a prctl() function?

 (*) Clean up/consolidate the security functions.  Possibly add a
     validation hook to be called at the same time as the mount context
     validate op.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=mount-context

David
---
David Howells (23):
      Provide a function to create a NUL-terminated string from unterminated data
      VFS: Clean up whitespace in fs/namespace.c
      VFS: Make get_mnt_ns() return the namespace
      VFS: Make get_filesystem() return the affected filesystem
      VFS: Provide empty name qstr
      Provide supplementary error message facility
      VFS: Introduce the structs and doc for a superblock configuration context
      VFS: Add LSM hooks for superblock configuration context
      VFS: Implement a superblock configuration context
      VFS: Remove unused code after superblock config context changes
      VFS: Implement fsopen() to prepare for a mount
      VFS: Implement fsmount() to effect a pre-configured mount
      VFS: Add a sample program for fsopen/fsmount
      procfs: Move proc_fill_super() to fs/proc/root.c
      proc: Add superblock config support to procfs
      NFS: Move sb-configuration bits into their own file
      NFS: Constify mount argument match tables
      NFS: Rename struct nfs_parsed_mount_data to struct nfs_sb_config
      NFS: Split nfs_parse_mount_options()
      NFS: Deindent nfs_sb_config_parse_option()
      NFS: Add a small buffer in nfs_sb_config to avoid string dup
      NFS: Do some tidying of the parsing code
      NFS: Add sb_config support.


 Documentation/filesystems/mounting.txt |  470 ++++++++
 arch/x86/entry/syscalls/syscall_32.tbl |    2 
 arch/x86/entry/syscalls/syscall_64.tbl |    2 
 fs/Makefile                            |    3 
 fs/dcache.c                            |    8 
 fs/filesystems.c                       |    3 
 fs/fsopen.c                            |  267 +++++
 fs/gfs2/dir.c                          |    3 
 fs/internal.h                          |    4 
 fs/libfs.c                             |   17 
 fs/mount.h                             |    3 
 fs/namei.c                             |    3 
 fs/namespace.c                         |  489 +++++++--
 fs/nfs/Makefile                        |    2 
 fs/nfs/client.c                        |   74 +
 fs/nfs/getroot.c                       |   72 +
 fs/nfs/internal.h                      |  130 +-
 fs/nfs/mount.c                         | 1499 +++++++++++++++++++++++++++
 fs/nfs/namespace.c                     |   71 +
 fs/nfs/nfs3_fs.h                       |    2 
 fs/nfs/nfs3client.c                    |    6 
 fs/nfs/nfs3proc.c                      |    2 
 fs/nfs/nfs4_fs.h                       |    4 
 fs/nfs/nfs4client.c                    |   82 +
 fs/nfs/nfs4namespace.c                 |  208 ++--
 fs/nfs/nfs4proc.c                      |    3 
 fs/nfs/nfs4super.c                     |  220 ++--
 fs/nfs/proc.c                          |    2 
 fs/nfs/super.c                         | 1785 ++------------------------------
 fs/nsfs.c                              |    3 
 fs/pipe.c                              |    3 
 fs/proc/inode.c                        |   50 -
 fs/proc/internal.h                     |    6 
 fs/proc/root.c                         |  211 +++-
 fs/sb_config.c                         |  512 +++++++++
 fs/super.c                             |  110 +-
 include/linux/dcache.h                 |    5 
 include/linux/fs.h                     |   16 
 include/linux/lsm_hooks.h              |   47 +
 include/linux/mount.h                  |    4 
 include/linux/nfs_xdr.h                |    7 
 include/linux/sb_config.h              |   87 ++
 include/linux/sched.h                  |   29 +
 include/linux/security.h               |   39 +
 include/linux/string.h                 |    1 
 include/linux/syscalls.h               |    3 
 include/uapi/linux/magic.h             |    1 
 include/uapi/linux/prctl.h             |    6 
 kernel/exit.c                          |    1 
 kernel/fork.c                          |    1 
 kernel/sys.c                           |   38 +
 kernel/sys_ni.c                        |    4 
 mm/util.c                              |   24 
 samples/fsmount/test-fsmount.c         |   92 ++
 security/security.c                    |   35 +
 security/selinux/hooks.c               |  201 +++-
 56 files changed, 4601 insertions(+), 2371 deletions(-)
 create mode 100644 Documentation/filesystems/mounting.txt
 create mode 100644 fs/fsopen.c
 create mode 100644 fs/nfs/mount.c
 create mode 100644 fs/sb_config.c
 create mode 100644 include/linux/sb_config.h
 create mode 100644 samples/fsmount/test-fsmount.c

^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH 01/23] Provide a function to create a NUL-terminated string from unterminated data [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
@ 2017-05-22 15:51 ` David Howells
  2017-05-22 15:51 ` [PATCH 02/23] VFS: Clean up whitespace in fs/namespace.c " David Howells
                   ` (25 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:51 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Provide a function, kmemdup_nul(), that will create a NUL-terminated string
from an unterminated character array where the length is known in advance.

This is better than kstrndup() in situations where we already know the
string length as the strnlen() in kstrndup() is superfluous.

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

 include/linux/string.h |    1 +
 mm/util.c              |   24 ++++++++++++++++++++++++
 2 files changed, 25 insertions(+)

diff --git a/include/linux/string.h b/include/linux/string.h
index 537918f8a98e..3dd944cfe171 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -131,6 +131,7 @@ extern char *kstrdup(const char *s, gfp_t gfp) __malloc;
 extern const char *kstrdup_const(const char *s, gfp_t gfp);
 extern char *kstrndup(const char *s, size_t len, gfp_t gfp);
 extern void *kmemdup(const void *src, size_t len, gfp_t gfp);
+extern char *kmemdup_nul(const char *s, size_t len, gfp_t gfp);
 
 extern char **argv_split(gfp_t gfp, const char *str, int *argcp);
 extern void argv_free(char **argv);
diff --git a/mm/util.c b/mm/util.c
index 464df3489903..cc9f0a2810bd 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -83,6 +83,8 @@ EXPORT_SYMBOL(kstrdup_const);
  * @s: the string to duplicate
  * @max: read at most @max chars from @s
  * @gfp: the GFP mask used in the kmalloc() call when allocating memory
+ *
+ * Note: Use kmemdup_nul() instead if the size is known exactly.
  */
 char *kstrndup(const char *s, size_t max, gfp_t gfp)
 {
@@ -121,6 +123,28 @@ void *kmemdup(const void *src, size_t len, gfp_t gfp)
 EXPORT_SYMBOL(kmemdup);
 
 /**
+ * kmemdup_nul - Create a NUL-terminated string from unterminated data
+ * @s: The data to stringify
+ * @len: The size of the data
+ * @gfp: the GFP mask used in the kmalloc() call when allocating memory
+ */
+char *kmemdup_nul(const char *s, size_t len, gfp_t gfp)
+{
+	char *buf;
+
+	if (!s)
+		return NULL;
+
+	buf = kmalloc_track_caller(len + 1, gfp);
+	if (buf) {
+		memcpy(buf, s, len);
+		buf[len] = '\0';
+	}
+	return buf;
+}
+EXPORT_SYMBOL(kmemdup_nul);
+
+/**
  * memdup_user - duplicate memory region from user space
  *
  * @src: source address in user space

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 02/23] VFS: Clean up whitespace in fs/namespace.c [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
  2017-05-22 15:51 ` [PATCH 01/23] Provide a function to create a NUL-terminated string from unterminated data " David Howells
@ 2017-05-22 15:51 ` David Howells
  2017-05-22 15:51 ` [PATCH 03/23] VFS: Make get_mnt_ns() return the namespace " David Howells
                   ` (24 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:51 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Clean up line terminal whitespace in fs/namespace.c.

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

 fs/namespace.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 8bd3e4d448b9..c076787871e7 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1656,7 +1656,7 @@ void __detach_mounts(struct dentry *dentry)
 	namespace_unlock();
 }
 
-/* 
+/*
  * Is the caller allowed to modify his namespace?
  */
 static inline bool may_mount(void)
@@ -2210,7 +2210,7 @@ static int do_loopback(struct path *path, const char *old_name,
 
 	err = -EINVAL;
 	if (mnt_ns_loop(old_path.dentry))
-		goto out; 
+		goto out;
 
 	mp = lock_mount(path);
 	err = PTR_ERR(mp);

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 03/23] VFS: Make get_mnt_ns() return the namespace [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
  2017-05-22 15:51 ` [PATCH 01/23] Provide a function to create a NUL-terminated string from unterminated data " David Howells
  2017-05-22 15:51 ` [PATCH 02/23] VFS: Clean up whitespace in fs/namespace.c " David Howells
@ 2017-05-22 15:51 ` David Howells
  2017-05-22 15:52 ` [PATCH 04/23] VFS: Make get_filesystem() return the affected filesystem " David Howells
                   ` (23 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:51 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Make get_mnt_ns() return the namespace it got a ref on for consistency with
other namespace ref getting functions.

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

 fs/mount.h |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/mount.h b/fs/mount.h
index bf1fda6eed8f..02ef292a77d7 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -108,9 +108,10 @@ static inline void detach_mounts(struct dentry *dentry)
 	__detach_mounts(dentry);
 }
 
-static inline void get_mnt_ns(struct mnt_namespace *ns)
+static inline struct mnt_namespace *get_mnt_ns(struct mnt_namespace *ns)
 {
 	atomic_inc(&ns->count);
+	return ns;
 }
 
 extern seqlock_t mount_lock;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 04/23] VFS: Make get_filesystem() return the affected filesystem [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (2 preceding siblings ...)
  2017-05-22 15:51 ` [PATCH 03/23] VFS: Make get_mnt_ns() return the namespace " David Howells
@ 2017-05-22 15:52 ` David Howells
  2017-05-22 15:52 ` [PATCH 05/23] VFS: Provide empty name qstr " David Howells
                   ` (22 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:52 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Make get_filesystem() return a pointer to the filesystem on which it just
got a ref.

Suggested-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/filesystems.c   |    3 ++-
 include/linux/fs.h |    2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/fs/filesystems.c b/fs/filesystems.c
index cac75547d35c..591e52d23ed4 100644
--- a/fs/filesystems.c
+++ b/fs/filesystems.c
@@ -33,9 +33,10 @@ static struct file_system_type *file_systems;
 static DEFINE_RWLOCK(file_systems_lock);
 
 /* WARNING: This can be used only if we _already_ own a reference */
-void get_filesystem(struct file_system_type *fs)
+struct file_system_type *get_filesystem(struct file_system_type *fs)
 {
 	__module_get(fs->owner);
+	return fs;
 }
 
 void put_filesystem(struct file_system_type *fs)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 803e5a9b2654..bc0c054894b9 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2956,7 +2956,7 @@ extern int generic_block_fiemap(struct inode *inode,
 				struct fiemap_extent_info *fieinfo, u64 start,
 				u64 len, get_block_t *get_block);
 
-extern void get_filesystem(struct file_system_type *fs);
+extern struct file_system_type *get_filesystem(struct file_system_type *fs);
 extern void put_filesystem(struct file_system_type *fs);
 extern struct file_system_type *get_fs_type(const char *name);
 extern struct super_block *get_super(struct block_device *);

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 05/23] VFS: Provide empty name qstr [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (3 preceding siblings ...)
  2017-05-22 15:52 ` [PATCH 04/23] VFS: Make get_filesystem() return the affected filesystem " David Howells
@ 2017-05-22 15:52 ` David Howells
  2017-05-22 15:52 ` [PATCH 06/23] Provide supplementary error message facility " David Howells
                   ` (21 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:52 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Provide an empty name (ie. "") qstr for general use.

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

 fs/dcache.c            |    8 ++++++--
 fs/gfs2/dir.c          |    3 +--
 fs/namei.c             |    3 +--
 fs/nsfs.c              |    3 +--
 fs/pipe.c              |    3 +--
 include/linux/dcache.h |    5 +++++
 6 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index cddf39777835..d3da2a3ffd73 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -90,6 +90,11 @@ EXPORT_SYMBOL(rename_lock);
 
 static struct kmem_cache *dentry_cache __read_mostly;
 
+const struct qstr empty_name = QSTR_INIT("", 0);
+EXPORT_SYMBOL(empty_name);
+const struct qstr slash_name = QSTR_INIT("/", 1);
+EXPORT_SYMBOL(slash_name);
+
 /*
  * This is the single most critical data structure when it comes
  * to the dcache: the hashtable for lookups. Somebody should try
@@ -1580,8 +1585,7 @@ struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 	 */
 	dentry->d_iname[DNAME_INLINE_LEN-1] = 0;
 	if (unlikely(!name)) {
-		static const struct qstr anon = QSTR_INIT("/", 1);
-		name = &anon;
+		name = &slash_name;
 		dname = dentry->d_iname;
 	} else if (name->len > DNAME_INLINE_LEN-1) {
 		size_t size = offsetof(struct external_name, name[1]);
diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c
index 79113219be5f..a5dfff6a033e 100644
--- a/fs/gfs2/dir.c
+++ b/fs/gfs2/dir.c
@@ -872,7 +872,6 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh,
 	struct buffer_head *bh;
 	struct gfs2_leaf *leaf;
 	struct gfs2_dirent *dent;
-	struct qstr name = { .name = "" };
 	struct timespec tv = current_time(inode);
 
 	error = gfs2_alloc_blocks(ip, &bn, &n, 0, NULL);
@@ -896,7 +895,7 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh,
 	leaf->lf_sec = cpu_to_be64(tv.tv_sec);
 	memset(leaf->lf_reserved2, 0, sizeof(leaf->lf_reserved2));
 	dent = (struct gfs2_dirent *)(leaf+1);
-	gfs2_qstr2dirent(&name, bh->b_size - sizeof(struct gfs2_leaf), dent);
+	gfs2_qstr2dirent(&empty_name, bh->b_size - sizeof(struct gfs2_leaf), dent);
 	*pbh = bh;
 	return leaf;
 }
diff --git a/fs/namei.c b/fs/namei.c
index 6571a5f5112e..0d35760fee00 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3400,7 +3400,6 @@ static int do_last(struct nameidata *nd,
 
 struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag)
 {
-	static const struct qstr name = QSTR_INIT("/", 1);
 	struct dentry *child = NULL;
 	struct inode *dir = dentry->d_inode;
 	struct inode *inode;
@@ -3414,7 +3413,7 @@ struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag)
 	if (!dir->i_op->tmpfile)
 		goto out_err;
 	error = -ENOMEM;
-	child = d_alloc(dentry, &name);
+	child = d_alloc(dentry, &slash_name);
 	if (unlikely(!child))
 		goto out_err;
 	error = dir->i_op->tmpfile(dir, child, mode);
diff --git a/fs/nsfs.c b/fs/nsfs.c
index f3db56e83dd2..08127a2b8559 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -53,7 +53,6 @@ static void nsfs_evict(struct inode *inode)
 static void *__ns_get_path(struct path *path, struct ns_common *ns)
 {
 	struct vfsmount *mnt = nsfs_mnt;
-	struct qstr qname = { .name = "", };
 	struct dentry *dentry;
 	struct inode *inode;
 	unsigned long d;
@@ -85,7 +84,7 @@ static void *__ns_get_path(struct path *path, struct ns_common *ns)
 	inode->i_fop = &ns_file_operations;
 	inode->i_private = ns;
 
-	dentry = d_alloc_pseudo(mnt->mnt_sb, &qname);
+	dentry = d_alloc_pseudo(mnt->mnt_sb, &empty_name);
 	if (!dentry) {
 		iput(inode);
 		return ERR_PTR(-ENOMEM);
diff --git a/fs/pipe.c b/fs/pipe.c
index 73b84baf58f8..97e5be897753 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -739,13 +739,12 @@ int create_pipe_files(struct file **res, int flags)
 	struct inode *inode = get_pipe_inode();
 	struct file *f;
 	struct path path;
-	static struct qstr name = { .name = "" };
 
 	if (!inode)
 		return -ENFILE;
 
 	err = -ENOMEM;
-	path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name);
+	path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &empty_name);
 	if (!path.dentry)
 		goto err_inode;
 	path.mnt = mntget(pipe_mnt);
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d2e38dc6172c..3f65a4fa72ed 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -55,6 +55,11 @@ struct qstr {
 
 #define QSTR_INIT(n,l) { { { .len = l } }, .name = n }
 
+extern const char empty_string[];
+extern const struct qstr empty_name;
+extern const char slash_string[];
+extern const struct qstr slash_name;
+
 struct dentry_stat_t {
 	long nr_dentry;
 	long nr_unused;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 06/23] Provide supplementary error message facility [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (4 preceding siblings ...)
  2017-05-22 15:52 ` [PATCH 05/23] VFS: Provide empty name qstr " David Howells
@ 2017-05-22 15:52 ` David Howells
  2017-05-22 15:52 ` [PATCH 07/23] VFS: Introduce the structs and doc for a superblock configuration context " David Howells
                   ` (20 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:52 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Provide a way for the kernel to pass supplementary error messages to
userspace.  This will make it easier for userspace, particularly in
containers to find out what went wrong during mounts and automounts, but is
also made available to any other syscalls that want to use it.

Two prctl() functions are added for this:

 (1) int old_setting = prctl(PR_ERRMSG_ENABLE, int setting);

     Enable (setting == 1) or disable (setting == 0) the facility.
     Disabling the facility clears the error buffer.

 (2) int size = prctl(PR_ERRMSG_READ, char *buffer, int buf_size);

     Reads the next error string into the buffer.  The string is truncated
     if it won't fit.  Strings are discarded as they're read.

     If there isn't a string, ENODATA is indicated.

I've done it this way rather than a proc file because procfs might not be
accessible.

The interface inside the kernel is a pair of macros:

 (*) void errorf(const char *fmt, ...);
 (*) int invalf(const char *fmt, ...);

Both of them snprintf() the string into the current process's error message
buffer if the facility is enabled.  The string is truncated if it exceeds
the limit.  invalf() returns -EINVAL whereas errof() has no return.

Note that this is very crude and could be made to store multiple strings,
allocate storage as required and not duplicate unformatted strings that are
stored in the rodata section (like kvasprintf_const).  Unfortunately,
specially handling rodata strings wouldn't gain a lot as most strings are
likely to be in modules, where the string's life can be terminated by
rmmod.

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

 include/linux/sched.h      |   29 +++++++++++++++++++++++++++++
 include/uapi/linux/prctl.h |    6 ++++++
 kernel/exit.c              |    1 +
 kernel/fork.c              |    1 +
 kernel/sys.c               |   38 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 75 insertions(+)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index 2b69fc650201..eba196521562 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1051,6 +1051,8 @@ struct task_struct {
 	/* Used by LSM modules for access restriction: */
 	void				*security;
 #endif
+#define ERROR_MSG_SIZE 256
+	char				*error_msg;
 	/* CPU-specific state of this task: */
 	struct thread_struct		thread;
 
@@ -1573,4 +1575,31 @@ extern long sched_getaffinity(pid_t pid, struct cpumask *mask);
 #define TASK_SIZE_OF(tsk)	TASK_SIZE
 #endif
 
+/**
+ * errorf - Store supplementary error message
+ * fmt: The format string
+ *
+ * Store the supplementary error message for the process if the process has
+ * enabled the facility.
+ */
+#define errorf(fmt, ...)			\
+	do {					\
+		if (current->error_msg)					\
+			snprintf(current->error_msg, ERROR_MSG_SIZE, fmt, ## __VA_ARGS__); \
+	} while(0)
+
+/**
+ * invalf - Store supplementary invalid argument error message
+ * fmt: The format string
+ *
+ * Store the supplementary error message for the process if the process has
+ * enabled the facility and return -EINVAL.
+ */
+#define invalf(fmt, ...)			\
+	({					\
+		errorf(fmt, ## __VA_ARGS__);	\
+		-EINVAL;			\
+	})
+
+
 #endif
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index a8d0759a9e40..b1203850dac8 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -197,4 +197,10 @@ struct prctl_mm_map {
 # define PR_CAP_AMBIENT_LOWER		3
 # define PR_CAP_AMBIENT_CLEAR_ALL	4
 
+/*
+ * Control the supplementary error message gathering facility.
+ */
+#define PR_ERRMSG_ENABLE		48
+#define PR_ERRMSG_READ			49
+
 #endif /* _LINUX_PRCTL_H */
diff --git a/kernel/exit.c b/kernel/exit.c
index 516acdb0e0ec..31b8617aee04 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -932,6 +932,7 @@ void __noreturn do_exit(long code)
 		__this_cpu_add(dirty_throttle_leaks, tsk->nr_dirtied);
 	exit_rcu();
 	TASKS_RCU(__srcu_read_unlock(&tasks_rcu_exit_srcu, tasks_rcu_i));
+	kfree(tsk->error_msg);
 
 	do_task_dead();
 }
diff --git a/kernel/fork.c b/kernel/fork.c
index 06d759ab4c62..aec6672d3f0e 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1903,6 +1903,7 @@ static __latent_entropy struct task_struct *copy_process(
 
 	trace_task_newtask(p, clone_flags);
 	uprobe_copy_process(p, clone_flags);
+	p->error_msg = NULL;
 
 	return p;
 
diff --git a/kernel/sys.c b/kernel/sys.c
index 8a94b4eabcaa..b784905c4806 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -2295,6 +2295,44 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_GET_FP_MODE:
 		error = GET_FP_MODE(me);
 		break;
+
+	case PR_ERRMSG_ENABLE:
+		switch (arg2) {
+		case 0:
+			if (!current->error_msg)
+				return 0;
+			kfree(current->error_msg);
+			current->error_msg = NULL;
+			return 1;
+		case 1:
+			if (current->error_msg)
+				return 1;
+			current->error_msg = kmalloc(ERROR_MSG_SIZE, GFP_KERNEL);
+			if (!current->error_msg)
+				return -ENOMEM;
+			current->error_msg[0] = 0;
+			return 0;
+		default:
+			error = -EINVAL;
+			break;
+		}
+		break;
+
+	case PR_ERRMSG_READ:
+		if (!arg2 || !arg3)
+			return -EINVAL;
+		if (!current->error_msg)
+			return -EINVAL;
+		if (!current->error_msg[0])
+			return -ENODATA;
+		error = strlen(current->error_msg);
+		if (arg3 < error)
+			error = arg3;
+		if (copy_to_user((char __user *)arg2, current->error_msg, error))
+			return -EFAULT;
+		current->error_msg[0] = 0;
+		return error;
+
 	default:
 		error = -EINVAL;
 		break;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 07/23] VFS: Introduce the structs and doc for a superblock configuration context [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (5 preceding siblings ...)
  2017-05-22 15:52 ` [PATCH 06/23] Provide supplementary error message facility " David Howells
@ 2017-05-22 15:52 ` David Howells
  2017-05-22 15:52 ` [PATCH 08/23] VFS: Add LSM hooks for " David Howells
                   ` (19 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:52 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Introduce a superblock configuration context concept to be used during
superblock creation for mount and superblock reconfiguration for remount.
This is allocated at the beginning of the mount procedure and into it is
placed:

 (1) Filesystem type.

 (2) Namespaces.

 (3) Device name.

 (4) Superblock flags (MS_*).

 (5) Security details.

 (6) Filesystem-specific data, as set by the mount options.

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

 Documentation/filesystems/mounting.txt |  470 ++++++++++++++++++++++++++++++++
 include/linux/sb_config.h              |   72 +++++
 2 files changed, 542 insertions(+)
 create mode 100644 Documentation/filesystems/mounting.txt
 create mode 100644 include/linux/sb_config.h

diff --git a/Documentation/filesystems/mounting.txt b/Documentation/filesystems/mounting.txt
new file mode 100644
index 000000000000..077ac8352323
--- /dev/null
+++ b/Documentation/filesystems/mounting.txt
@@ -0,0 +1,470 @@
+			      ===================
+			      FILESYSTEM MOUNTING
+			      ===================
+
+CONTENTS
+
+ (1) Overview.
+
+ (2) The superblock configuration context.
+
+ (3) The superblock config operations.
+
+ (4) Superblock config security.
+
+ (5) VFS superblock config operations.
+
+
+========
+OVERVIEW
+========
+
+The creation of new mounts is now to be done in a multistep process:
+
+ (1) Create a superblock configuration context.
+
+ (2) Parse the options and attach them to the context.  Options may be passed
+     individually from userspace.
+
+ (3) Validate and pre-process the context.
+
+ (4) Get or create a superblock and mountable root.
+
+ (5) Perform the mount.
+
+ (6) Return an error message attached to the context.
+
+ (7) Destroy the context.
+
+To support this, the file_system_type struct gains two new fields:
+
+	unsigned short sb_config_size;
+
+which indicates the total amount of space that should be allocated for context
+data (see the Superblock Configuration Context section), and:
+
+	int (*init_sb_config)(struct sb_config *sc, struct super_block *src_sb);
+
+which is invoked to set up the filesystem-specific parts of a superblock
+configuration context, including the additional space.  The src_sb parameter is
+used to convey the superblock from which the filesystem may draw extra
+information (such as namespaces), for submount (SB_CONFIG_FOR_SUBMOUNT) or
+remount (SB_CONFIG_FOR_REMOUNT) purposes or it will be NULL.
+
+Note that security initialisation is done *after* the filesystem is called so
+that the namespaces may be adjusted first.
+
+And the super_operations struct gains one:
+
+	int (*remount_fs_sc) (struct super_block *, struct sb_config *);
+
+This shadows the ->remount_fs() operation and takes a prepared superblock
+configuration context instead of the mount flags and data page.  It may modify
+the ms_flags in the context for the caller to pick up.
+
+[NOTE] remount_fs_sc is intended as a replacement for remount_fs.
+
+
+====================================
+THE SUPERBLOCK CONFIGURATION CONTEXT
+====================================
+
+The creation and reconfiguration of a superblock is governed by a superblock
+configuration context.  This is represented by the sb_config structure:
+
+	struct sb_config {
+		const struct sb_config_operations *ops;
+		struct file_system_type *fs;
+		struct dentry		*root;
+		struct user_namespace	*user_ns;
+		struct net		*net_ns;
+		const struct cred	*cred;
+		char			*device;
+		void			*security;
+		const char		*error_msg;
+		unsigned int		ms_flags;
+		bool			sloppy;
+		bool			silent;
+		bool			degraded;
+		enum mount_type		mount_type : 8;
+	};
+
+When the VFS creates this, it allocates ->sb_config_size bytes (as specified by
+the file_system_type object) to hold both the sb_config struct and any extra
+data required by the filesystem.  The sb_config struct is placed at the
+beginning of this space.  Any extra space beyond that is for use by the
+filesystem.  The filesystem should wrap the struct in its own, e.g.:
+
+	struct nfs_sb_config {
+		struct sb_config sc;
+		...
+	};
+
+placing the sb_config struct first.  container_of() can then be used.  The
+file_system_type would be initialised thus:
+
+	struct file_system_type nfs = {
+		...
+		.sb_config_size	= sizeof(struct nfs_sb_config),
+		.init_sb_config	= nfs_init_sb_config,
+		...
+	};
+
+The sb_config fields are as follows:
+
+ (*) const struct sb_config_operations *ops
+
+     These are operations that can be done on a superblock configuration
+     context (see below).  This must be set by the ->init_sb_config()
+     file_system_type operation.
+
+ (*) struct file_system_type *fs
+
+     A pointer to the file_system_type of the filesystem that is being
+     constructed or reconfigured.  This retains a ref on the type owner.
+
+ (*) struct dentry *root
+
+     A pointer to the root of the mountable tree (and indirectly, the
+     superblock thereof).  This is filled in by the ->get_tree() op.
+
+ (*) struct user_namespace *user_ns
+ (*) struct net *net_ns
+
+     This is a subset of the namespaces in use by the invoking process.  This
+     retains a ref on each namespace.  The subscribed namespaces may be
+     replaced by the filesystem to reflect other sources, such as the parent
+     mount superblock on an automount.
+
+ (*) struct cred *cred
+
+     The mounter's credentials.  This retains a ref on the credentials.
+
+ (*) char *device
+
+     This is the device to be mounted.  It may be a block device
+     (e.g. /dev/sda1) or something more exotic, such as the "host:/path" that
+     NFS desires.
+
+ (*) void *security
+
+     A place for the LSMs to hang their security data for the superblock.  The
+     relevant security operations are described below.
+
+ (*) const char *error_msg
+
+     A place for the VFS and the filesystem to hang an error message.  This
+     should be in the form of a static string that doesn't need deallocation
+     and the pointer to which can just be overwritten.  Under some
+     circumstances, this can be retrieved by userspace.
+
+     Note that the existence of the error string is expected to be guaranteed
+     by the reference on the file_system_type object held by ->fs or any
+     filesystem-specific reference held in the filesystem context until the
+     ->free() operation is called.
+
+     Use sb_cfg_error() and sb_cfg_inval() to set this rather than setting it
+     directly.
+
+ (*) unsigned int ms_flags
+
+     This holds the MS_* flags mount flags.
+
+ (*) bool sloppy
+ (*) bool silent
+
+     These are set if the sloppy or silent mount options are given.
+
+     [NOTE] sloppy is probably unnecessary when userspace passes over one
+     option at a time since the error can just be ignored if userspace deems it
+     to be unimportant.
+
+     [NOTE] silent is probably redundant with ms_flags & MS_SILENT.
+
+ (*) bool degraded
+
+     This is set if any preallocated resources in the configuration have been
+     used up, thereby rendering the configuration unreusable for the
+     ->get_tree() op.
+
+ (*) enum mount_type
+
+     This indicates the type of mount operation.  The available values are:
+
+	SB_CONFIG_FOR_NEW	-- New mount
+	SB_CONFIG_FOR_SUBMOUNT	-- New automatic submount of extant mount
+	SB_CONFIG_FOR_REMOUNT	-- Change an existing mount
+
+The mount context is created by calling __vfs_new_sb_config(),
+vfs_new_sb_config(), vfs_sb_reconfig() or vfs_dup_sb_config() and is destroyed
+with put_sb_config().  Note that the structure is not refcounted.
+
+VFS, security and filesystem mount options are set individually with
+vfs_parse_mount_option() or in bulk with generic_monolithic_mount_data().
+
+When mounting, the filesystem is allowed to take data from any of the pointers
+and attach it to the superblock (or whatever), provided it clears the pointer
+in the mount context.
+
+The filesystem is also allowed to allocate resources and pin them with the
+mount context.  For instance, NFS might pin the appropriate protocol version
+module.
+
+
+================================
+THE SUPERBLOCK CONFIG OPERATIONS
+================================
+
+The superblock configuration context points to a table of operations:
+
+	struct sb_config_operations {
+		void (*free)(struct sb_config *sc);
+		int (*dup)(struct sb_config *sc, struct sb_config *src_sc);
+		int (*parse_option)(struct sb_config *sc, char *p);
+		int (*monolithic_mount_data)(struct sb_config *sc, void *data);
+		int (*validate)(struct sb_config *sc);
+		int (*get_tree)(struct sb_config *sc);
+	};
+
+These operations are invoked by the various stages of the mount procedure to
+manage the superblock configuration context.  They are as follows:
+
+ (*) void (*free)(struct sb_config *sc);
+
+     Called to clean up the filesystem-specific part of the superblock
+     configuration context when the context is destroyed.  It should be aware
+     that parts of the context may have been removed and NULL'd out by
+     ->mount().
+
+ (*) int (*dup)(struct sb_config *sc, struct sb_config *src_sc);
+
+     Called when a superblock configuration context has been duplicated to get
+     any refs or copy any non-referenced resources held in the
+     filesystem-specific part of the superblock configuration context.  An
+     error may be returned to indicate failure to do this.
+
+     [!] Note that even if this fails, put_sb_config() will be called
+	 immediately thereafter, so ->dup() *must* make the filesystem-specific
+	 part safe for ->free().
+
+ (*) int (*parse_option)(struct sb_config *sc, char *p);
+
+     Called when an option is to be added to the superblock configuration
+     context.  p points to the option string, likely in "key[=val]" format.
+     VFS-specific options will have been weeded out and sc->ms_flags updated in
+     the context.  Security options will also have been weeded out and
+     sc->security updated.
+
+     If successful, 0 should be returned and a negative error code otherwise.
+     If an ambiguous error (such as -EINVAL) is returned, sb_cfg_error() or
+     sb_cfg_inval() should be used to provide a string that provides more
+     information.
+
+ (*) int (*monolithic_mount_data)(struct sb_config *sc, void *data);
+
+     Called when the mount(2) system call is invoked to pass the entire data
+     page in one go.  If this is expected to be just a list of "key[=val]"
+     items separated by commas, then this may be set to NULL.
+
+     The return value is as for ->parse_option().
+
+     If the filesystem (eg. NFS) needs to examine the data first and then
+     finds it's the standard key-val list then it may pass it off to:
+
+	int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+
+ (*) int (*validate)(struct sb_config *sc);
+
+     Called when all the options have been applied and the mount is about to
+     take place.  It is should check for inconsistencies from mount options
+     and it is also allowed to do preliminary resource acquisition.  For
+     instance, the core NFS module could load the NFS protocol module here.
+
+     Note that if sc->mount_type == SB_CONFIG_FOR_REMOUNT, some of the options
+     necessary for a new mount may not be set.
+
+     The return value is as for ->parse_option().
+
+ (*) int (*get_tree)(struct sb_config *sc);
+
+     Called to get or create the mountable root and superblock, using the
+     information stored in the superblock configuration context (remounts go
+     via a different vector).  It may detach any resources it desires from the
+     superblock configuration context and transfer them to the superblock it
+     creates.
+
+     On success it should set sc->root to the mountable root.
+
+     In the case of an error, it should return a negative error code and
+     consider invoking sb_cfg_inval() or sb_cfg_error().
+
+
+=========================================
+SUPERBLOCK CONFIGURATION CONTEXT SECURITY
+========================================
+
+The superblock configuration context contains a security points that the LSMs can use for
+building up a security context for the superblock to be mounted.  There are a
+number of operations used by the new mount code for this purpose:
+
+ (*) int security_sb_config_alloc(struct sb_config *sc,
+				  struct super_block *src_sb);
+
+     Called to initialise sc->security (which is preset to NULL) and allocate
+     any resources needed.  It should return 0 on success and a negative error
+     code on failure.
+
+     src_sb is non-NULL in the case of a remount (SB_CONFIG_FOR_REMOUNT) in
+     which case it indicates the superblock to be remounted or in the case of a
+     submount (SB_CONFIG_FOR_SUBMOUNT) in which case it indicates the parent
+     superblock.
+
+ (*) int security_sb_config_dup(struct sb_config *sc,
+				struct sb_config *src_mc);
+
+     Called to initialise sc->security (which is preset to NULL) and allocate
+     any resources needed.  The original superblock configuration context is pointed to by src_mc
+     and may be used for reference.  It should return 0 on success and a
+     negative error code on failure.
+
+ (*) void security_sb_config_free(struct sb_config *sc);
+
+     Called to clean up anything attached to sc->security.  Note that the
+     contents may have been transferred to a superblock and the pointer NULL'd
+     out during mount.
+
+ (*) int security_sb_config_parse_option(struct sb_config *sc, char *opt);
+
+     Called for each mount option.  The mount options are in "key[=val]"
+     form.  An active LSM may reject one with an error, pass one over and
+     return 0 or consume one and return 1.  If consumed, the option isn't
+     passed on to the filesystem.
+
+     If it returns an error, more information can be returned with
+     sb_cfg_inval() or sb_cfg_error().
+
+ (*) int security_sb_get_tree(struct sb_config *sc);
+
+     Called during the mount procedure to verify that the specified superblock
+     is allowed to be mounted and to transfer the security data there.
+
+     On success, it should return 0; otherwise it should return an error and
+     perhaps call sb_cfg_inval() or sb_cfg_error() to indicate the problem.  It
+     should not return -ENOMEM as this should be taken care of in advance.
+
+     [NOTE] Should I add a security_sb_config_validate() operation so that the
+     LSM has the opportunity to allocate stuff and check the options as a
+     whole?
+
+
+================================
+VFS SUPERBLOCK CONFIG OPERATIONS
+================================
+
+There are four operations for creating a superblock configuration context and
+one for destroying a context:
+
+ (*) struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
+					   struct super_block *src_sb;
+					   unsigned int ms_flags);
+
+     Create a superblock configuration context given a filesystem type pointer.
+     This allocates the superblock configuration context, sets the flags,
+     initialises the security and calls fs_type->init_sb_config() to initialise
+     the filesystem context.
+
+     src_sb can be NULL or it may indicate a superblock that is going to be
+     remounted (SB_CONFIG_FOR_REMOUNT) or a superblock that is the parent of a
+     submount (SB_CONFIG_FOR_SUBMOUNT).  This superblock is provided as a
+     source of namespace information.
+
+ (*) struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
+				       unsigned int ms_flags);
+
+     Create a superblock configuration context from the same filesystem as an
+     extant mount and initialise the mount parameters from the superblock
+     underlying that mount.  This is for use by remount.
+
+ (*) struct sb_config *vfs_new_sb_config(const char *fs_name);
+
+     Create a superblock configuration context given a filesystem name.  It is
+     assumed that the mount flags will be passed in as text options or set
+     directly later.  This is intended to be called from sys_mount() or
+     sys_fsopen().  This copies current's namespaces to the superblock
+     configuration context.
+
+ (*) struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc);
+
+     Duplicate a superblock configuration context, copying any options noted
+     and duplicating or additionally referencing any resources held therein.
+     This is available for use where a filesystem has to get a mount within a
+     mount, such as NFS4 does by internally mounting the root of the target
+     server and then doing a private pathwalk to the target directory.
+
+ (*) void put_sb_config(struct sb_config *sc);
+
+     Destroy a superblock configuration context, releasing any resources it
+     holds.  This calls the ->free() operation.  This is intended to be called
+     by anyone who created a superblock configuration context.
+
+     [!] superblock configuration contexts are not refcounted, so this causes
+	 unconditional destruction.
+
+In all the above operations, apart from the put op, the return is a mount
+context pointer or a negative error code.  No error string is saved as the
+error string is only guaranteed as long as the file_system_type is pinned (and
+thus the module).
+
+The next operations can be used to cache an error message in the context for
+the caller to collect.
+
+ (*) void sb_cfg_error(struct sb_config *sc, const char *msg);
+
+     Set an error message for the caller to pick up.  For lifetime rules, see
+     the ->error_msg member description.
+
+ (*) void sb_cfg_inval(struct sb_config *sc, const char *msg);
+
+     As sb_cfg_error(), but returns -EINVAL for use with tail calling.
+
+In the remaining operations, if an error occurs, a negative error code is
+returned and, if not obvious, sc->error_msg may have been set to point to a
+useful string.  This string should not be freed.
+
+ (*) int vfs_get_tree(struct sb_config *sc);
+
+     Get or create the mountable root and superblock, using the parameters in
+     the parsed configuration to select/configure the superblock.  This invokes
+     the ->validate() op and then the ->get_tree() op.
+
+     [NOTE] ->validate() can probably be rolled into ->get_tree() and
+     ->remount_fs_sc().
+
+ (*) struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc);
+
+     Create a mount given the parameters in the specified superblock
+     configuration context.
+
+ (*) struct vfsmount *vfs_submount_sc(const struct dentry *mountpoint,
+				      struct sb_config *sc);
+
+     Create a mount given a superblock configuration context and set
+     MS_SUBMOUNT on it.  A wrapper around vfs_kern_mount_sc().  This is
+     intended to be called from filesystems that have automount points (NFS,
+     AFS, ...).
+
+ (*) int vfs_parse_mount_option(struct sb_config *sc, char *data);
+
+     Supply a single mount option to the superblock configuration context.  The
+     mount option should likely be in a "key[=val]" string form.  The option is
+     first checked to see if it corresponds to a standard mount flag (in which
+     case it is used to mark an MS_xxx flag and consumed) or a security option
+     (in which case the LSM consumes it) before it is passed on to the
+     filesystem.
+
+ (*) int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+
+     Parse a sys_mount() data page, assuming the form to be a text list
+     consisting of key[=val] options separated by commas.  Each item in the
+     list is passed to vfs_mount_option().  This is the default when the
+     ->monolithic_mount_data() operation is NULL.
diff --git a/include/linux/sb_config.h b/include/linux/sb_config.h
new file mode 100644
index 000000000000..d2af7342a082
--- /dev/null
+++ b/include/linux/sb_config.h
@@ -0,0 +1,72 @@
+/* Superblock configuration and creation handling.
+ *
+ * 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.
+ */
+
+#ifndef _LINUX_SB_CONFIG_H
+#define _LINUX_SB_CONFIG_H
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+
+struct cred;
+struct dentry;
+struct file_operations;
+struct file_system_type;
+struct mnt_namespace;
+struct net;
+struct pid_namespace;
+struct super_block;
+struct user_namespace;
+struct vfsmount;
+
+enum sb_config_purpose {
+	SB_CONFIG_FOR_NEW,	/* New superblock for direct mount */
+	SB_CONFIG_FOR_SUBMOUNT,	/* New superblock for automatic submount */
+	SB_CONFIG_FOR_REMOUNT,	/* Superblock reconfiguration for remount */
+};
+
+/*
+ * Superblock configuration context as allocated and constructed by the
+ * ->init_sb_config() file_system_type operation.  The size of the object
+ * allocated is specified in struct file_system_type::sb_config_size and this
+ * must include sufficient space for the sb_config struct.
+ *
+ * Superblock creation fills in ->root whereas reconfiguration begins with this
+ * already set.
+ *
+ * See Documentation/filesystems/mounting.txt
+ */
+struct sb_config {
+	const struct sb_config_operations *ops;
+	struct file_system_type	*fs_type;
+	struct dentry		*root;		/* The root and superblock */
+	struct user_namespace	*user_ns;	/* The user namespace for this mount */
+	struct net		*net_ns;	/* The network namespace for this mount */
+	const struct cred	*cred;		/* The mounter's credentials */
+	char			*device;	/* The device name or mount target */
+	char			*subtype;	/* The subtype to set on the superblock */
+	void			*security;	/* The LSM context */
+	unsigned int		ms_flags;	/* The superblock flags (MS_*) */
+	bool			sloppy;		/* Unrecognised options are okay */
+	bool			silent;
+	bool			degraded;	/* True if the config can't be reused */
+	enum sb_config_purpose 	purpose : 8;
+};
+
+struct sb_config_operations {
+	void (*free)(struct sb_config *sc);
+	int (*dup)(struct sb_config *sc, struct sb_config *src);
+	int (*parse_option)(struct sb_config *sc, char *p);
+	int (*monolithic_mount_data)(struct sb_config *sc, void *data);
+	int (*validate)(struct sb_config *sc);
+	int (*get_tree)(struct sb_config *sc);
+};
+
+#endif /* _LINUX_SB_CONFIG_H */

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 08/23] VFS: Add LSM hooks for superblock configuration context [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (6 preceding siblings ...)
  2017-05-22 15:52 ` [PATCH 07/23] VFS: Introduce the structs and doc for a superblock configuration context " David Howells
@ 2017-05-22 15:52 ` David Howells
  2017-05-22 15:53 ` [PATCH 09/23] VFS: Implement a " David Howells
                   ` (18 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:52 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Add LSM hooks for use by the superblock configuration context code.

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

 include/linux/lsm_hooks.h |   39 ++++++++++
 include/linux/security.h  |   28 +++++++
 security/security.c       |   25 +++++++
 security/selinux/hooks.c  |  169 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 261 insertions(+)

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 080f34e66017..c2bbd9e92b0a 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -75,6 +75,34 @@
  *	should enable secure mode.
  *	@bprm contains the linux_binprm structure.
  *
+ * Security hooks for mount using fd context.
+ *
+ * @sb_config_alloc:
+ *	Allocate and attach a security structure to sc->security.  This pointer
+ *	is initialised to NULL by the caller.
+ *	@sc indicates the new superblock configuration context.
+ *	@src_sb indicates the source superblock of a submount.
+ * @sb_config_dup:
+ *	Allocate and attach a security structure to sc->security.  This pointer
+ *	is initialised to NULL by the caller.
+ *	@sc indicates the new superblock configuration context.
+ *	@src_sc indicates the original superblock configuration context.
+ * @sb_config_free:
+ *	Clean up a superblock configuration context.
+ *	@sc indicates the superblock configuration context.
+ * @sb_config_parse_option:
+ *	Userspace provided an option to configure a superblock.  The LSM may
+ *	reject it with an error and may use it for itself, in which case it
+ *	should return 1; otherwise it should return 0 to pass it on to the
+ *	filesystem.
+ *	@sc indicates the superblock configuration context.
+ *	@p indicates the option in "key[=val]" form.
+ * @sb_get_tree:
+ *	Assign the security to a newly created superblock.
+ *	@sc indicates the superblock configuration context.
+ *	@sc->root indicates the root that will be mounted.
+ *	@sc->root->d_sb points to the superblock.
+ *
  * Security hooks for filesystem operations.
  *
  * @sb_alloc_security:
@@ -1372,6 +1400,12 @@ union security_list_options {
 	void (*bprm_committing_creds)(struct linux_binprm *bprm);
 	void (*bprm_committed_creds)(struct linux_binprm *bprm);
 
+	int (*sb_config_alloc)(struct sb_config *sc, struct super_block *src_sb);
+	int (*sb_config_dup)(struct sb_config *sc, struct sb_config *src_sc);
+	void (*sb_config_free)(struct sb_config *sc);
+	int (*sb_config_parse_option)(struct sb_config *sc, char *opt);
+	int (*sb_get_tree)(struct sb_config *sc);
+
 	int (*sb_alloc_security)(struct super_block *sb);
 	void (*sb_free_security)(struct super_block *sb);
 	int (*sb_copy_data)(char *orig, char *copy);
@@ -1683,6 +1717,11 @@ struct security_hook_heads {
 	struct list_head bprm_secureexec;
 	struct list_head bprm_committing_creds;
 	struct list_head bprm_committed_creds;
+	struct list_head sb_config_alloc;
+	struct list_head sb_config_dup;
+	struct list_head sb_config_free;
+	struct list_head sb_config_parse_option;
+	struct list_head sb_get_tree;
 	struct list_head sb_alloc_security;
 	struct list_head sb_free_security;
 	struct list_head sb_copy_data;
diff --git a/include/linux/security.h b/include/linux/security.h
index af675b576645..d1dfb6abd4f7 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -55,6 +55,7 @@ struct msg_queue;
 struct xattr;
 struct xfrm_sec_ctx;
 struct mm_struct;
+struct sb_config;
 
 /* If capable should audit the security request */
 #define SECURITY_CAP_NOAUDIT 0
@@ -224,6 +225,11 @@ int security_bprm_check(struct linux_binprm *bprm);
 void security_bprm_committing_creds(struct linux_binprm *bprm);
 void security_bprm_committed_creds(struct linux_binprm *bprm);
 int security_bprm_secureexec(struct linux_binprm *bprm);
+int security_sb_config_alloc(struct sb_config *sc, struct super_block *sb);
+int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc);
+void security_sb_config_free(struct sb_config *sc);
+int security_sb_config_parse_option(struct sb_config *sc, char *opt);
+int security_sb_get_tree(struct sb_config *sc);
 int security_sb_alloc(struct super_block *sb);
 void security_sb_free(struct super_block *sb);
 int security_sb_copy_data(char *orig, char *copy);
@@ -520,6 +526,28 @@ static inline int security_bprm_secureexec(struct linux_binprm *bprm)
 	return cap_bprm_secureexec(bprm);
 }
 
+static inline int security_sb_config_alloc(struct sb_config *sc,
+					   struct super_block *src_sb)
+{
+	return 0;
+}
+static inline int security_sb_config_dup(struct sb_config *sc,
+					 struct sb_config *src_sc)
+{
+	return 0;
+}
+static inline void security_sb_config_free(struct sb_config *sc)
+{
+}
+static inline int security_sb_config_parse_option(struct sb_config *sc, char *opt)
+{
+	return 0;
+}
+static inline int security_sb_get_tree(struct sb_config *sc)
+{
+	return 0;
+}
+
 static inline int security_sb_alloc(struct super_block *sb)
 {
 	return 0;
diff --git a/security/security.c b/security/security.c
index b9fea3999cf8..951f28487719 100644
--- a/security/security.c
+++ b/security/security.c
@@ -316,6 +316,31 @@ int security_bprm_secureexec(struct linux_binprm *bprm)
 	return call_int_hook(bprm_secureexec, 0, bprm);
 }
 
+int security_sb_config_alloc(struct sb_config *sc, struct super_block *src_sb)
+{
+	return call_int_hook(sb_config_alloc, 0, sc, src_sb);
+}
+
+int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc)
+{
+	return call_int_hook(sb_config_dup, 0, sc, src_sc);
+}
+
+void security_sb_config_free(struct sb_config *sc)
+{
+	call_void_hook(sb_config_free, sc);
+}
+
+int security_sb_config_parse_option(struct sb_config *sc, char *opt)
+{
+	return call_int_hook(sb_config_parse_option, 0, sc, opt);
+}
+
+int security_sb_get_tree(struct sb_config *sc)
+{
+	return call_int_hook(sb_get_tree, 0, sc);
+}
+
 int security_sb_alloc(struct super_block *sb)
 {
 	return call_int_hook(sb_alloc_security, 0, sb);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e67a526d1f30..420bfa955fb4 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -47,6 +47,7 @@
 #include <linux/fdtable.h>
 #include <linux/namei.h>
 #include <linux/mount.h>
+#include <linux/sb_config.h>
 #include <linux/netfilter_ipv4.h>
 #include <linux/netfilter_ipv6.h>
 #include <linux/tty.h>
@@ -2826,6 +2827,168 @@ static int selinux_umount(struct vfsmount *mnt, int flags)
 				   FILESYSTEM__UNMOUNT, NULL);
 }
 
+/* fsopen mount context operations */
+
+static int selinux_sb_config_alloc(struct sb_config *sc,
+				   struct super_block *src_sb)
+{
+	struct security_mnt_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return -ENOMEM;
+
+	sc->security = opts;
+	return 0;
+}
+
+static int selinux_sb_config_dup(struct sb_config *sc,
+				 struct sb_config *src_sc)
+{
+	const struct security_mnt_opts *src = src_sc->security;
+	struct security_mnt_opts *opts;
+	int i, n;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return -ENOMEM;
+	sc->security = opts;
+
+	if (!src || !src->num_mnt_opts)
+		return 0;
+	n = opts->num_mnt_opts = src->num_mnt_opts;
+
+	if (src->mnt_opts) {
+		opts->mnt_opts = kcalloc(n, sizeof(char *), GFP_KERNEL);
+		if (!opts->mnt_opts)
+			return -ENOMEM;
+
+		for (i = 0; i < n; i++) {
+			if (src->mnt_opts[i]) {
+				opts->mnt_opts[i] = kstrdup(src->mnt_opts[i],
+							    GFP_KERNEL);
+				if (!opts->mnt_opts[i])
+					return -ENOMEM;
+			}
+		}
+	}
+
+	if (src->mnt_opts_flags) {
+		opts->mnt_opts_flags = kmemdup(src->mnt_opts_flags,
+					       n * sizeof(int), GFP_KERNEL);
+		if (!opts->mnt_opts_flags)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void selinux_sb_config_free(struct sb_config *sc)
+{
+	struct security_mnt_opts *opts = sc->security;
+
+	security_free_mnt_opts(opts);
+	sc->security = NULL;
+}
+
+static int selinux_sb_config_parse_option(struct sb_config *sc, char *opt)
+{
+	struct security_mnt_opts *opts = sc->security;
+	substring_t args[MAX_OPT_ARGS];
+	unsigned int have;
+	char *c, **oo;
+	int token, ctx, i, *of;
+
+	token = match_token(opt, tokens, args);
+	if (token == Opt_error)
+		return 0; /* Doesn't belong to us. */
+
+	have = 0;
+	for (i = 0; i < opts->num_mnt_opts; i++)
+		have |= 1 << opts->mnt_opts_flags[i];
+	if (have & (1 << token))
+		return invalf("SELinux: Duplicate mount options");
+
+	switch (token) {
+	case Opt_context:
+		if (have & (1 << Opt_defcontext))
+			goto incompatible;
+		ctx = CONTEXT_MNT;
+		goto copy_context_string;
+
+	case Opt_fscontext:
+		ctx = FSCONTEXT_MNT;
+		goto copy_context_string;
+
+	case Opt_rootcontext:
+		ctx = ROOTCONTEXT_MNT;
+		goto copy_context_string;
+
+	case Opt_defcontext:
+		if (have & (1 << Opt_context))
+			goto incompatible;
+		ctx = DEFCONTEXT_MNT;
+		goto copy_context_string;
+
+	case Opt_labelsupport:
+		return 1;
+
+	default:
+		return invalf("SELinux: Unknown mount option");
+	}
+
+copy_context_string:
+	if (opts->num_mnt_opts > 3)
+		return invalf("SELinux: Too many options");
+
+	of = krealloc(opts->mnt_opts_flags,
+		      (opts->num_mnt_opts + 1) * sizeof(int), GFP_KERNEL);
+	if (!of)
+		return -ENOMEM;
+	of[opts->num_mnt_opts] = 0;
+	opts->mnt_opts_flags = of;
+
+	oo = krealloc(opts->mnt_opts,
+		      (opts->num_mnt_opts + 1) * sizeof(char *), GFP_KERNEL);
+	if (!oo)
+		return -ENOMEM;
+	oo[opts->num_mnt_opts] = NULL;
+	opts->mnt_opts = oo;
+
+	c = match_strdup(&args[0]);
+	if (!c)
+		return -ENOMEM;
+	opts->mnt_opts[opts->num_mnt_opts] = c;
+	opts->mnt_opts_flags[opts->num_mnt_opts] = ctx;
+	opts->num_mnt_opts++;
+	return 1;
+
+incompatible:
+	return invalf("SELinux: Incompatible mount options");
+}
+
+static int selinux_sb_get_tree(struct sb_config *sc)
+{
+	const struct cred *cred = current_cred();
+	struct common_audit_data ad;
+	int rc;
+
+	rc = selinux_set_mnt_opts(sc->root->d_sb, sc->security, 0, NULL);
+	if (rc)
+		return rc;
+
+	/* Allow all mounts performed by the kernel */
+	if (sc->ms_flags & MS_KERNMOUNT)
+		return 0;
+
+	ad.type = LSM_AUDIT_DATA_DENTRY;
+	ad.u.dentry = sc->root;
+	rc = superblock_has_perm(cred, sc->root->d_sb, FILESYSTEM__MOUNT, &ad);
+	if (rc < 0)
+		errorf("SELinux: Mount of superblock not permitted");
+	return rc;
+}
+
 /* inode security operations */
 
 static int selinux_inode_alloc_security(struct inode *inode)
@@ -6154,6 +6317,12 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
 	LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds),
 	LSM_HOOK_INIT(bprm_secureexec, selinux_bprm_secureexec),
 
+	LSM_HOOK_INIT(sb_config_alloc, selinux_sb_config_alloc),
+	LSM_HOOK_INIT(sb_config_dup, selinux_sb_config_dup),
+	LSM_HOOK_INIT(sb_config_free, selinux_sb_config_free),
+	LSM_HOOK_INIT(sb_config_parse_option, selinux_sb_config_parse_option),
+	LSM_HOOK_INIT(sb_get_tree, selinux_sb_get_tree),
+
 	LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
 	LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
 	LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data),

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 09/23] VFS: Implement a superblock configuration context [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (7 preceding siblings ...)
  2017-05-22 15:52 ` [PATCH 08/23] VFS: Add LSM hooks for " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 10/23] VFS: Remove unused code after superblock config context changes " David Howells
                   ` (17 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Implement a superblock configuration context concept to be used during
superblock creation for mount and superblock reconfiguration for remount.

The mounting procedure then becomes:

 (1) Allocate new sb_config context.

 (2) Configure the context.

 (3) Create superblock.

 (4) Mount the superblock any number of times.

 (5) Destroy the context.

Rather than calling fs_type->mount(), an sb_config struct is created and
fs_type->init_sb_config() is called to set it up.  fs_type->sb_config_size
says how much space should be allocated for the config context.  The
sb_config struct is placed at the beginning and any extra space is for the
filesystem's use.

A set of operations has to be set by ->init_sb_config() to provide freeing,
duplication, option parsing, binary data parsing, validation, mounting and
superblock filling.

Legacy filesystems are supported by the provision of a set of legacy
sb_config operations that build up a list of mount options and then invoke
fs_type->mount() from within the sb_config ->get_tree() operation.  This
allows all filesystems to be accessed using sb_config.

It should be noted that, whilst this patch adds a lot of lines of code,
there is quite a bit of duplication with existing code that can be
eliminated should all filesystems be converted over.

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

 fs/Makefile               |    3 
 fs/internal.h             |    2 
 fs/libfs.c                |   17 +
 fs/namespace.c            |  389 ++++++++++++++++++++++++++--------
 fs/sb_config.c            |  512 +++++++++++++++++++++++++++++++++++++++++++++
 fs/super.c                |   57 +++++
 include/linux/fs.h        |   14 +
 include/linux/mount.h     |    4 
 include/linux/sb_config.h |   13 +
 9 files changed, 907 insertions(+), 104 deletions(-)
 create mode 100644 fs/sb_config.c

diff --git a/fs/Makefile b/fs/Makefile
index 7bbaca9c67b1..8f5142525866 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -11,7 +11,8 @@ obj-y :=	open.o read_write.o file_table.o super.o \
 		attr.o bad_inode.o file.o filesystems.o namespace.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o \
 		pnode.o splice.o sync.o utimes.o \
-		stack.o fs_struct.o statfs.o fs_pin.o nsfs.o
+		stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
+		sb_config.o
 
 ifeq ($(CONFIG_BLOCK),y)
 obj-y +=	buffer.o block_dev.o direct-io.o mpage.o
diff --git a/fs/internal.h b/fs/internal.h
index 9676fe11c093..39121a99d930 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -87,7 +87,7 @@ extern struct file *get_empty_filp(void);
 /*
  * super.c
  */
-extern int do_remount_sb(struct super_block *, int, void *, int);
+extern int do_remount_sb(struct super_block *, int, void *, int, struct sb_config *);
 extern bool trylock_super(struct super_block *sb);
 extern struct dentry *mount_fs(struct file_system_type *,
 			       int, const char *, void *);
diff --git a/fs/libfs.c b/fs/libfs.c
index a04395334bb1..e8787adf0363 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -9,6 +9,7 @@
 #include <linux/slab.h>
 #include <linux/cred.h>
 #include <linux/mount.h>
+#include <linux/sb_config.h>
 #include <linux/vfs.h>
 #include <linux/quotaops.h>
 #include <linux/mutex.h>
@@ -574,13 +575,27 @@ static DEFINE_SPINLOCK(pin_fs_lock);
 
 int simple_pin_fs(struct file_system_type *type, struct vfsmount **mount, int *count)
 {
+	struct sb_config *sc;
 	struct vfsmount *mnt = NULL;
+	int ret;
+
 	spin_lock(&pin_fs_lock);
 	if (unlikely(!*mount)) {
 		spin_unlock(&pin_fs_lock);
-		mnt = vfs_kern_mount(type, MS_KERNMOUNT, type->name, NULL);
+
+		sc = __vfs_new_sb_config(type, NULL, MS_KERNMOUNT, SB_CONFIG_FOR_NEW);
+		if (IS_ERR(sc))
+			return PTR_ERR(sc);
+
+		ret = vfs_get_tree(sc);
+		if (ret < 0)
+			return ret;
+
+		mnt = vfs_kern_mount_sc(sc);
+		put_sb_config(sc);
 		if (IS_ERR(mnt))
 			return PTR_ERR(mnt);
+
 		spin_lock(&pin_fs_lock);
 		if (!*mount)
 			*mount = mnt;
diff --git a/fs/namespace.c b/fs/namespace.c
index c076787871e7..e92bc48accb5 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -25,7 +25,9 @@
 #include <linux/magic.h>
 #include <linux/bootmem.h>
 #include <linux/task_work.h>
+#include <linux/file.h>
 #include <linux/sched/task.h>
+#include <linux/sb_config.h>
 
 #include "pnode.h"
 #include "internal.h"
@@ -957,55 +959,6 @@ static struct mount *skip_mnt_tree(struct mount *p)
 	return p;
 }
 
-struct vfsmount *
-vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
-{
-	struct mount *mnt;
-	struct dentry *root;
-
-	if (!type)
-		return ERR_PTR(-ENODEV);
-
-	mnt = alloc_vfsmnt(name);
-	if (!mnt)
-		return ERR_PTR(-ENOMEM);
-
-	if (flags & MS_KERNMOUNT)
-		mnt->mnt.mnt_flags = MNT_INTERNAL;
-
-	root = mount_fs(type, flags, name, data);
-	if (IS_ERR(root)) {
-		mnt_free_id(mnt);
-		free_vfsmnt(mnt);
-		return ERR_CAST(root);
-	}
-
-	mnt->mnt.mnt_root = root;
-	mnt->mnt.mnt_sb = root->d_sb;
-	mnt->mnt_mountpoint = mnt->mnt.mnt_root;
-	mnt->mnt_parent = mnt;
-	lock_mount_hash();
-	list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
-	unlock_mount_hash();
-	return &mnt->mnt;
-}
-EXPORT_SYMBOL_GPL(vfs_kern_mount);
-
-struct vfsmount *
-vfs_submount(const struct dentry *mountpoint, struct file_system_type *type,
-	     const char *name, void *data)
-{
-	/* Until it is worked out how to pass the user namespace
-	 * through from the parent mount to the submount don't support
-	 * unprivileged mounts with submounts.
-	 */
-	if (mountpoint->d_sb->s_user_ns != &init_user_ns)
-		return ERR_PTR(-EPERM);
-
-	return vfs_kern_mount(type, MS_SUBMOUNT, name, data);
-}
-EXPORT_SYMBOL_GPL(vfs_submount);
-
 static struct mount *clone_mnt(struct mount *old, struct dentry *root,
 					int flag)
 {
@@ -1593,7 +1546,7 @@ static int do_umount(struct mount *mnt, int flags)
 			return -EPERM;
 		down_write(&sb->s_umount);
 		if (!(sb->s_flags & MS_RDONLY))
-			retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
+			retval = do_remount_sb(sb, MS_RDONLY, NULL, 0, NULL);
 		up_write(&sb->s_umount);
 		return retval;
 	}
@@ -2276,6 +2229,20 @@ static int change_mount_flags(struct vfsmount *mnt, int ms_flags)
 }
 
 /*
+ * Parse the monolithic page of mount data given to sys_mount().
+ */
+static int parse_monolithic_mount_data(struct sb_config *sc, void *data)
+{
+	int (*monolithic_mount_data)(struct sb_config *, void *);
+
+	monolithic_mount_data = sc->ops->monolithic_mount_data;
+	if (!monolithic_mount_data)
+		monolithic_mount_data = generic_monolithic_mount_data;
+
+	return monolithic_mount_data(sc, data);
+}
+
+/*
  * change filesystem flags. dir should be a physical root of filesystem.
  * If you've mounted a non-root directory somewhere and want to do remount
  * on it - tough luck.
@@ -2283,9 +2250,11 @@ static int change_mount_flags(struct vfsmount *mnt, int ms_flags)
 static int do_remount(struct path *path, int flags, int mnt_flags,
 		      void *data)
 {
+	struct sb_config *sc = NULL;
 	int err;
 	struct super_block *sb = path->mnt->mnt_sb;
 	struct mount *mnt = real_mount(path->mnt);
+	struct file_system_type *type = sb->s_type;
 
 	if (!check_mnt(mnt))
 		return -EINVAL;
@@ -2320,9 +2289,19 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
 		return -EPERM;
 	}
 
-	err = security_sb_remount(sb, data);
-	if (err)
-		return err;
+	if (type->init_sb_config) {
+		sc = vfs_sb_reconfig(path->mnt, flags);
+		if (IS_ERR(sc))
+			return PTR_ERR(sc);
+
+		err = parse_monolithic_mount_data(sc, data);
+		if (err < 0)
+			goto err_sc;
+	} else {
+		err = security_sb_remount(sb, data);
+		if (err)
+			return err;
+	}
 
 	down_write(&sb->s_umount);
 	if (flags & MS_BIND)
@@ -2330,7 +2309,7 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
 	else if (!capable(CAP_SYS_ADMIN))
 		err = -EPERM;
 	else
-		err = do_remount_sb(sb, flags, data, 0);
+		err = do_remount_sb(sb, flags, data, 0, sc);
 	if (!err) {
 		lock_mount_hash();
 		mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK;
@@ -2339,6 +2318,9 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
 		unlock_mount_hash();
 	}
 	up_write(&sb->s_umount);
+err_sc:
+	if (sc)
+		put_sb_config(sc);
 	return err;
 }
 
@@ -2422,29 +2404,6 @@ static int do_move_mount(struct path *path, const char *old_name)
 	return err;
 }
 
-static struct vfsmount *fs_set_subtype(struct vfsmount *mnt, const char *fstype)
-{
-	int err;
-	const char *subtype = strchr(fstype, '.');
-	if (subtype) {
-		subtype++;
-		err = -EINVAL;
-		if (!subtype[0])
-			goto err;
-	} else
-		subtype = "";
-
-	mnt->mnt_sb->s_subtype = kstrdup(subtype, GFP_KERNEL);
-	err = -ENOMEM;
-	if (!mnt->mnt_sb->s_subtype)
-		goto err;
-	return mnt;
-
- err:
-	mntput(mnt);
-	return ERR_PTR(err);
-}
-
 /*
  * add a mount into a namespace's mount tree
  */
@@ -2492,40 +2451,80 @@ static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags)
 static bool mount_too_revealing(struct vfsmount *mnt, int *new_mnt_flags);
 
 /*
+ * Create a new mount using a superblock configuration and request it
+ * be added to the namespace tree.
+ */
+static int do_new_mount_sc(struct sb_config *sc, struct path *mountpoint,
+			   unsigned int mnt_flags)
+{
+	struct vfsmount *mnt;
+	int ret;
+
+	mnt = vfs_kern_mount_sc(sc);
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);
+
+	ret = -EPERM;
+	if (mount_too_revealing(mnt, &mnt_flags)) {
+		errorf("VFS: Mount too revealing");
+		goto err_mnt;
+	}
+
+	ret = do_add_mount(real_mount(mnt), mountpoint, mnt_flags);
+	if (ret < 0) {
+		errorf("VFS: Failed to add mount");
+		goto err_mnt;
+	}
+	return ret;
+
+err_mnt:
+	mntput(mnt);
+	return ret;
+}
+
+/*
  * create a new mount for userspace and request it to be added into the
  * namespace's tree
  */
-static int do_new_mount(struct path *path, const char *fstype, int flags,
+static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
 			int mnt_flags, const char *name, void *data)
 {
-	struct file_system_type *type;
-	struct vfsmount *mnt;
+	struct sb_config *sc;
 	int err;
 
 	if (!fstype)
 		return -EINVAL;
 
-	type = get_fs_type(fstype);
-	if (!type)
-		return -ENODEV;
+	sc = vfs_new_sb_config(fstype);
+	if (IS_ERR(sc)) {
+		err = PTR_ERR(sc);
+		goto err;
+	}
+	sc->ms_flags = flags;
 
-	mnt = vfs_kern_mount(type, flags, name, data);
-	if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
-	    !mnt->mnt_sb->s_subtype)
-		mnt = fs_set_subtype(mnt, fstype);
+	err = -ENOMEM;
+	sc->device = kstrdup(name, GFP_KERNEL);
+	if (!sc->device)
+		goto err_sc;
 
-	put_filesystem(type);
-	if (IS_ERR(mnt))
-		return PTR_ERR(mnt);
+	err = parse_monolithic_mount_data(sc, data);
+	if (err < 0)
+		goto err_sc;
 
-	if (mount_too_revealing(mnt, &mnt_flags)) {
-		mntput(mnt);
-		return -EPERM;
-	}
+	err = vfs_get_tree(sc);
+	if (err < 0)
+		goto err_sc;
 
-	err = do_add_mount(real_mount(mnt), path, mnt_flags);
+	err = do_new_mount_sc(sc, mountpoint, mnt_flags);
 	if (err)
-		mntput(mnt);
+		goto err_sc;
+
+	put_sb_config(sc);
+	return 0;
+
+err_sc:
+	put_sb_config(sc);
+err:
 	return err;
 }
 
@@ -3058,6 +3057,187 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
 	return ret;
 }
 
+/**
+ * vfs_get_tree - Get the mountable root
+ * @sc: The superblock configuration context.
+ *
+ * The filesystem is invoked to get or create a superblock which can then later
+ * be used for mounting.  The filesystem places a pointer to the root to be
+ * used for mounting in @sc->root.
+ */
+int vfs_get_tree(struct sb_config *sc)
+{
+	struct super_block *sb;
+	int ret;
+
+	if (sc->root)
+		return -EBUSY;
+
+	if (sc->ops->validate) {
+		ret = sc->ops->validate(sc);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* The filesystem may transfer preallocated resources from the
+	 * configuration context to the superblock, thereby rendering the
+	 * config unusable for another attempt at creation if this one fails.
+	 */
+	if (sc->degraded)
+		return invalf("VFS: The config is degraded");
+
+	/* Get the mountable root in sc->root, with a ref on the root and a ref
+	 * on the superblock.
+	 */
+	ret = sc->ops->get_tree(sc);
+	if (ret < 0)
+		return ret;
+
+	BUG_ON(!sc->root);
+	sb = sc->root->d_sb;
+	WARN_ON(!sb->s_bdi);
+
+	ret = security_sb_get_tree(sc);
+	if (ret < 0)
+		goto err_sb;
+
+	ret = -ENOMEM;
+	if (sc->subtype && !sb->s_subtype) {
+		sb->s_subtype = kstrdup(sc->subtype, GFP_KERNEL);
+		if (!sb->s_subtype)
+			goto err_sb;
+	}
+
+	sb->s_flags |= MS_BORN;
+
+	/* Filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
+	 * but s_maxbytes was an unsigned long long for many releases.  Throw
+	 * this warning for a little while to try and catch filesystems that
+	 * violate this rule.
+	 */
+	WARN(sb->s_maxbytes < 0,
+	     "%s set sb->s_maxbytes to negative value (%lld)\n",
+	     sc->fs_type->name, sb->s_maxbytes);
+
+	up_write(&sb->s_umount);
+	return 0;
+
+err_sb:
+	dput(sc->root);
+	sc->root = NULL;
+	deactivate_locked_super(sb);
+	return ret;
+}
+EXPORT_SYMBOL(vfs_get_tree);
+
+/**
+ * vfs_kern_mount_sc - Create a mount for a configured superblock
+ * sc: The configuration context with the superblock attached
+ *
+ * Create a mount to an already configured superblock.  If necessary, the
+ * caller should invoke vfs_create_super() before calling this.
+ */
+struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc)
+{
+	struct mount *mnt;
+
+	if (!sc->root)
+		return ERR_PTR(invalf("VFS: Root must be obtained before mount"));
+
+	mnt = alloc_vfsmnt(sc->device ?: "none");
+	if (!mnt)
+		return ERR_PTR(-ENOMEM);
+
+	if (sc->ms_flags & MS_KERNMOUNT)
+		mnt->mnt.mnt_flags = MNT_INTERNAL;
+
+	atomic_inc(&sc->root->d_sb->s_active);
+	mnt->mnt.mnt_sb		= sc->root->d_sb;
+	mnt->mnt.mnt_root	= dget(sc->root);
+	mnt->mnt_mountpoint	= mnt->mnt.mnt_root;
+	mnt->mnt_parent		= mnt;
+
+	lock_mount_hash();
+	list_add_tail(&mnt->mnt_instance, &mnt->mnt.mnt_sb->s_mounts);
+	unlock_mount_hash();
+	return &mnt->mnt;
+}
+EXPORT_SYMBOL_GPL(vfs_kern_mount_sc);
+
+struct vfsmount *vfs_kern_mount(struct file_system_type *type,
+				int flags, const char *name, void *data)
+{
+	struct sb_config *sc;
+	struct vfsmount *mnt;
+	int ret;
+
+	if (!type)
+		return ERR_PTR(-EINVAL);
+
+	sc = __vfs_new_sb_config(type, NULL, flags, SB_CONFIG_FOR_NEW);
+	if (IS_ERR(sc))
+		return ERR_CAST(sc);
+
+	if (name) {
+		ret = -ENOMEM;
+		sc->device = kstrdup(name, GFP_KERNEL);
+		if (!sc->device)
+			goto err_sc;
+	}
+
+	ret = parse_monolithic_mount_data(sc, data);
+	if (ret < 0)
+		goto err_sc;
+
+	ret = vfs_get_tree(sc);
+	if (ret < 0)
+		goto err_sc;
+
+	mnt = vfs_kern_mount_sc(sc);
+	if (IS_ERR(mnt)) {
+		ret = PTR_ERR(mnt);
+		goto err_sc;
+	}
+
+	put_sb_config(sc);
+	return mnt;
+
+err_sc:
+	put_sb_config(sc);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(vfs_kern_mount);
+
+struct vfsmount *
+vfs_submount_sc(const struct dentry *mountpoint, struct sb_config *sc)
+{
+	/* Until it is worked out how to pass the user namespace
+	 * through from the parent mount to the submount don't support
+	 * unprivileged mounts with submounts.
+	 */
+	if (mountpoint->d_sb->s_user_ns != &init_user_ns)
+		return ERR_PTR(-EPERM);
+
+	sc->ms_flags = MS_SUBMOUNT;
+	return vfs_kern_mount_sc(sc);
+}
+EXPORT_SYMBOL_GPL(vfs_submount_sc);
+
+struct vfsmount *
+vfs_submount(const struct dentry *mountpoint, struct file_system_type *type,
+	     const char *name, void *data)
+{
+	/* Until it is worked out how to pass the user namespace
+	 * through from the parent mount to the submount don't support
+	 * unprivileged mounts with submounts.
+	 */
+	if (mountpoint->d_sb->s_user_ns != &init_user_ns)
+		return ERR_PTR(-EPERM);
+
+	return vfs_kern_mount(type, MS_SUBMOUNT, name, data);
+}
+EXPORT_SYMBOL_GPL(vfs_submount);
+
 /*
  * Return true if path is reachable from root
  *
@@ -3299,6 +3479,23 @@ struct vfsmount *kern_mount_data(struct file_system_type *type, void *data)
 }
 EXPORT_SYMBOL_GPL(kern_mount_data);
 
+struct vfsmount *kern_mount_data_sc(struct sb_config *sc)
+{
+	struct vfsmount *mnt;
+
+	sc->ms_flags = MS_KERNMOUNT;
+	mnt = vfs_kern_mount_sc(sc);
+	if (!IS_ERR(mnt)) {
+		/*
+		 * it is a longterm mount, don't release mnt until
+		 * we unmount before file sys is unregistered
+		*/
+		real_mount(mnt)->mnt_ns = MNT_NS_INTERNAL;
+	}
+	return mnt;
+}
+EXPORT_SYMBOL_GPL(kern_mount_data_sc);
+
 void kern_unmount(struct vfsmount *mnt)
 {
 	/* release long term mount so mount point can be released */
diff --git a/fs/sb_config.c b/fs/sb_config.c
new file mode 100644
index 000000000000..4d9bfb982d41
--- /dev/null
+++ b/fs/sb_config.c
@@ -0,0 +1,512 @@
+/* Provide a way to create a superblock configuration context within the kernel
+ * that allows a superblock to be set up prior to mounting.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/sb_config.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/nsproxy.h>
+#include <linux/slab.h>
+#include <linux/magic.h>
+#include <linux/security.h>
+#include <linux/parser.h>
+#include <linux/mnt_namespace.h>
+#include <linux/pid_namespace.h>
+#include <linux/user_namespace.h>
+#include <net/net_namespace.h>
+#include "mount.h"
+
+struct legacy_sb_config {
+	struct sb_config	sc;
+	char			*legacy_data;	/* Data page for legacy filesystems */
+	char			*secdata;
+	unsigned int		data_usage;
+};
+
+static const struct sb_config_operations legacy_sb_config_ops;
+
+static const match_table_t common_set_mount_options = {
+	{ MS_DIRSYNC,		"dirsync" },
+	{ MS_I_VERSION,		"iversion" },
+	{ MS_LAZYTIME,		"lazytime" },
+	{ MS_MANDLOCK,		"mand" },
+	{ MS_POSIXACL,		"posixacl" },
+	{ MS_RDONLY,		"ro" },
+	{ MS_REC,		"rec" },
+	{ MS_SYNCHRONOUS,	"sync" },
+	{ MS_VERBOSE,		"verbose" },
+	{ },
+};
+
+static const match_table_t common_clear_mount_options = {
+	{ MS_LAZYTIME,		"nolazytime" },
+	{ MS_MANDLOCK,		"nomand" },
+	{ MS_RDONLY,		"rw" },
+	{ MS_SILENT,		"silent" },
+	{ MS_SYNCHRONOUS,	"async" },
+	{ },
+};
+
+static const match_table_t forbidden_mount_options = {
+	{ MS_BIND,		"bind" },
+	{ MS_MOVE,		"move" },
+	{ MS_PRIVATE,		"private" },
+	{ MS_REMOUNT,		"remount" },
+	{ MS_SHARED,		"shared" },
+	{ MS_SLAVE,		"slave" },
+	{ MS_UNBINDABLE,	"unbindable" },
+	{ MS_NOATIME,		"noatime" },
+	{ MS_RELATIME,		"relatime" },
+	{ MS_RELATIME,		"norelatime" },
+	{ MS_STRICTATIME,	"strictatime" },
+	{ MS_STRICTATIME,	"nostrictatime" },
+	{ MS_NODIRATIME,	"nodiratime" },
+	{ MS_NODEV,		"dev" },
+	{ MS_NODEV,		"nodev" },
+	{ MS_NOEXEC,		"exec" },
+	{ MS_NOEXEC,		"noexec" },
+	{ MS_NOSUID,		"suid" },
+	{ MS_NOSUID,		"nosuid" },
+	{ },
+};
+
+/*
+ * Check for a common mount option.
+ */
+static int vfs_parse_ms_mount_option(struct sb_config *sc, char *data)
+{
+	substring_t args[MAX_OPT_ARGS];
+	unsigned int token;
+
+	token = match_token(data, common_set_mount_options, args);
+	if (token) {
+		sc->ms_flags |= token;
+		return 1;
+	}
+
+	token = match_token(data, common_clear_mount_options, args);
+	if (token) {
+		sc->ms_flags &= ~token;
+		return 1;
+	}
+
+	token = match_token(data, forbidden_mount_options, args);
+	if (token)
+		return invalf("VFS: Mount option, not superblock option");
+
+	return 0;
+}
+
+/**
+ * vfs_parse_mount_option - Add a single mount option to a superblock config
+ * @mc: The superblock configuration to modify
+ * @p: The option to apply.
+ *
+ * A single mount option in string form is applied to the superblock
+ * configuration being set up.  Certain standard options (for example "ro") are
+ * translated into flag bits without going to the filesystem.  The active
+ * security module is allowed to observe and poach options.  Any other options
+ * are passed over to the filesystem to parse.
+ *
+ * This may be called multiple times for a context.
+ *
+ * Returns 0 on success and a negative error code on failure.  In the event of
+ * failure, sc->error may have been set to a non-allocated string that gives
+ * more information.
+ */
+int vfs_parse_mount_option(struct sb_config *sc, char *p)
+{
+	int ret;
+
+	ret = vfs_parse_ms_mount_option(sc, p);
+	if (ret < 0)
+		return ret;
+	if (ret == 1)
+		return 0;
+
+	ret = security_sb_config_parse_option(sc, p);
+	if (ret < 0)
+		return ret;
+	if (ret == 1)
+		return 0;
+
+	if (sc->ops->parse_option)
+		return sc->ops->parse_option(sc, p);
+
+	return invalf("VFS: FS takes no options");
+}
+EXPORT_SYMBOL(vfs_parse_mount_option);
+
+/**
+ * generic_monolithic_mount_data - Parse key[=val][,key[=val]]* mount data
+ * @mc: The superblock configuration to fill in.
+ * @data: The data to parse
+ *
+ * Parse a blob of data that's in key[=val][,key[=val]]* form.  This can be
+ * called from the ->monolithic_mount_data() sb_config operation.
+ *
+ * Returns 0 on success or the error returned by the ->parse_option() sb_config
+ * operation on failure.
+ */
+int generic_monolithic_mount_data(struct sb_config *ctx, void *data)
+{
+	char *options = data, *p;
+	int ret;
+
+	if (!options)
+		return 0;
+
+	while ((p = strsep(&options, ",")) != NULL) {
+		if (*p) {
+			ret = vfs_parse_mount_option(ctx, p);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(generic_monolithic_mount_data);
+
+/**
+ * __vfs_new_sb_config - Create a superblock config.
+ * @fs_type: The filesystem type.
+ * @src_sb: A superblock from which this one derives (or NULL)
+ * @ms_flags: Superblock flags and op flags (such as MS_REMOUNT)
+ * @purpose: The purpose that this configuration shall be used for.
+ *
+ * Open a filesystem and create a mount context.  The mount context is
+ * initialised with the supplied flags and, if a submount/automount from
+ * another superblock (@src_sb), may have parameters such as namespaces copied
+ * across from that superblock.
+ */
+struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
+				      struct super_block *src_sb,
+				      unsigned int ms_flags,
+				      enum sb_config_purpose purpose)
+{
+	struct sb_config *sc;
+	size_t sc_size = fs_type->sb_config_size;
+	int ret;
+
+	BUG_ON(fs_type->init_sb_config && sc_size < sizeof(*sc));
+
+	if (!fs_type->init_sb_config)
+		sc_size = sizeof(struct legacy_sb_config);
+
+	sc = kzalloc(sc_size, GFP_KERNEL);
+	if (!sc)
+		return ERR_PTR(-ENOMEM);
+
+	sc->purpose	= purpose;
+	sc->ms_flags	= ms_flags;
+	sc->fs_type	= get_filesystem(fs_type);
+	sc->net_ns	= get_net(current->nsproxy->net_ns);
+	sc->user_ns	= get_user_ns(current_user_ns());
+	sc->cred	= get_current_cred();
+
+	/* TODO: Make all filesystems support this unconditionally */
+	if (sc->fs_type->init_sb_config) {
+		ret = sc->fs_type->init_sb_config(sc, src_sb);
+		if (ret < 0)
+			goto err_sc;
+	} else {
+		sc->ops = &legacy_sb_config_ops;
+	}
+
+	/* Do the security check last because ->init_sb_config may change the
+	 * namespace subscriptions.
+	 */
+	ret = security_sb_config_alloc(sc, src_sb);
+	if (ret < 0)
+		goto err_sc;
+
+	return sc;
+
+err_sc:
+	put_sb_config(sc);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(__vfs_new_sb_config);
+
+/**
+ * vfs_new_sb_config - Create a superblock config for a new mount.
+ * @fs_name: The name of the filesystem
+ *
+ * Open a filesystem and create a superblock config context for a new mount
+ * that will hold the mount options, device name, security details, etc..  Note
+ * that the caller should check the ->ops pointer in the returned context to
+ * determine whether the filesystem actually supports the superblock context
+ * itself.
+ */
+struct sb_config *vfs_new_sb_config(const char *fs_name)
+{
+	struct file_system_type *fs_type;
+	struct sb_config *sc;
+
+	fs_type = get_fs_type(fs_name);
+	if (!fs_type)
+		return ERR_PTR(-ENODEV);
+
+	sc = __vfs_new_sb_config(fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
+	put_filesystem(fs_type);
+	return sc;
+}
+EXPORT_SYMBOL(vfs_new_sb_config);
+
+/**
+ * vfs_sb_reconfig - Create a superblock config for remount/reconfiguration
+ * @mnt: The mountpoint to open
+ * @ms_flags: Superblock flags and op flags (such as MS_REMOUNT)
+ *
+ * Open a mounted filesystem and create a mount context such that a remount can
+ * be effected.
+ */
+struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
+				  unsigned int ms_flags)
+{
+	return __vfs_new_sb_config(mnt->mnt_sb->s_type, mnt->mnt_sb,
+				   ms_flags, SB_CONFIG_FOR_REMOUNT);
+}
+
+/**
+ * vfs_dup_sc_config: Duplicate a superblock configuration context.
+ * @src_sc: The context to copy.
+ */
+struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc)
+{
+	struct sb_config *sc;
+	size_t sc_size;
+	int ret;
+
+	if (!src_sc->ops->dup)
+		return ERR_PTR(-ENOTSUPP);
+
+	sc_size = src_sc->fs_type->sb_config_size;
+	if (!src_sc->fs_type->init_sb_config)
+		sc_size = sizeof(struct legacy_sb_config);
+
+	sc = kmemdup(src_sc, src_sc->fs_type->sb_config_size, GFP_KERNEL);
+	if (!sc)
+		return ERR_PTR(-ENOMEM);
+
+	sc->device	= NULL;
+	sc->security	= NULL;
+	get_filesystem(sc->fs_type);
+	get_net(sc->net_ns);
+	get_user_ns(sc->user_ns);
+	get_cred(sc->cred);
+
+	/* Can't call put until we've called ->dup */
+	ret = sc->ops->dup(sc, src_sc);
+	if (ret < 0)
+		goto err_sc;
+
+	ret = security_sb_config_dup(sc, src_sc);
+	if (ret < 0)
+		goto err_sc;
+	return sc;
+
+err_sc:
+	put_sb_config(sc);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(vfs_dup_sb_config);
+
+/**
+ * put_sb_config - Dispose of a superblock configuration context.
+ * @sc: The context to dispose of.
+ */
+void put_sb_config(struct sb_config *sc)
+{
+	struct super_block *sb;
+
+	if (sc->root) {
+		sb = sc->root->d_sb;
+		dput(sc->root);
+		sc->root = NULL;
+		deactivate_super(sb);
+	}
+
+	if (sc->ops && sc->ops->free)
+		sc->ops->free(sc);
+
+	security_sb_config_free(sc);
+	if (sc->net_ns)
+		put_net(sc->net_ns);
+	put_user_ns(sc->user_ns);
+	if (sc->cred)
+		put_cred(sc->cred);
+	kfree(sc->subtype);
+	put_filesystem(sc->fs_type);
+	kfree(sc->device);
+	kfree(sc);
+}
+EXPORT_SYMBOL(put_sb_config);
+
+/*
+ * Free the config for a filesystem that doesn't support sb_config.
+ */
+static void legacy_sb_config_free(struct sb_config *sc)
+{
+	struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+
+	free_secdata(cfg->secdata);
+	kfree(cfg->legacy_data);
+}
+
+/*
+ * Duplicate a legacy config.
+ */
+static int legacy_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc)
+{
+	struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+	struct legacy_sb_config *src_cfg = container_of(src_sc, struct legacy_sb_config, sc);
+
+	cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!cfg->legacy_data)
+		return -ENOMEM;
+	memcpy(cfg->legacy_data, src_cfg->legacy_data, sizeof(PAGE_SIZE));
+	return 0;
+}
+
+/*
+ * Add an option to a legacy config.  We build up a comma-separated list of
+ * options.
+ */
+static int legacy_parse_option(struct sb_config *sc, char *p)
+{
+	struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+	unsigned int usage = cfg->data_usage;
+	size_t len = strlen(p);
+
+	if (len > PAGE_SIZE - 2 - usage)
+		return invalf("VFS: Insufficient data buffer space");
+	if (memchr(p, ',', len) != NULL)
+		return invalf("VFS: Options cannot contain commas");
+	if (!cfg->legacy_data) {
+		cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
+		if (!cfg->legacy_data)
+			return -ENOMEM;
+	}
+
+	cfg->legacy_data[usage++] = ',';
+	memcpy(cfg->legacy_data + usage, p, len);
+	usage += len;
+	cfg->legacy_data[usage] = '\0';
+	cfg->data_usage = usage;
+	return 0;
+}
+
+/*
+ * Add monolithic mount data.
+ */
+static int legacy_monolithic_mount_data(struct sb_config *sc, void *data)
+{
+	struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+
+	if (cfg->data_usage != 0)
+		return invalf("VFS: Can't mix monolithic and individual options");
+	if (!data)
+		return 0;
+	if (!cfg->legacy_data) {
+		cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
+		if (!cfg->legacy_data)
+			return -ENOMEM;
+	}
+
+	memcpy(cfg->legacy_data, data, PAGE_SIZE);
+	cfg->data_usage = PAGE_SIZE;
+	return 0;
+}
+
+/*
+ * Use the legacy mount validation step to strip out and process security
+ * config options.
+ */
+static int legacy_validate(struct sb_config *sc)
+{
+	struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+
+	if (!cfg->legacy_data || cfg->sc.fs_type->fs_flags & FS_BINARY_MOUNTDATA)
+		return 0;
+
+	cfg->secdata = alloc_secdata();
+	if (!cfg->secdata)
+		return -ENOMEM;
+
+	return security_sb_copy_data(cfg->legacy_data, cfg->secdata);
+}
+
+/*
+ * Determine the superblock subtype.
+ */
+static int legacy_set_subtype(struct sb_config *sc)
+{
+	const char *subtype = strchr(sc->fs_type->name, '.');
+
+	if (subtype) {
+		subtype++;
+		if (!subtype[0])
+			return -EINVAL;
+	} else {
+		subtype = "";
+	}
+
+	sc->subtype = kstrdup(subtype, GFP_KERNEL);
+	if (!sc->subtype)
+		return -ENOMEM;
+	return 0;
+}
+
+/*
+ * Get a mountable root with the legacy mount command.
+ */
+static int legacy_get_tree(struct sb_config *sc)
+{
+	struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+	struct super_block *sb;
+	struct dentry *root;
+	int ret;
+
+	root = cfg->sc.fs_type->mount(cfg->sc.fs_type, cfg->sc.ms_flags,
+				      cfg->sc.device, cfg->legacy_data);
+	if (IS_ERR(root))
+		return PTR_ERR(root);
+
+	sb = root->d_sb;
+	BUG_ON(!sb);
+
+	if ((cfg->sc.fs_type->fs_flags & FS_HAS_SUBTYPE) &&
+	    !sc->subtype) {
+		ret = legacy_set_subtype(sc);
+		if (ret < 0)
+			goto err_sb;
+	}
+
+	cfg->sc.root = root;
+	return 0;
+
+err_sb:
+	dput(root);
+	deactivate_locked_super(sb);
+	return ret;
+}
+
+static const struct sb_config_operations legacy_sb_config_ops = {
+	.free			= legacy_sb_config_free,
+	.dup			= legacy_sb_config_dup,
+	.parse_option		= legacy_parse_option,
+	.monolithic_mount_data	= legacy_monolithic_mount_data,
+	.validate		= legacy_validate,
+	.get_tree		= legacy_get_tree,
+};
diff --git a/fs/super.c b/fs/super.c
index adb0c0de428c..e4b47d481679 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -34,6 +34,7 @@
 #include <linux/fsnotify.h>
 #include <linux/lockdep.h>
 #include <linux/user_namespace.h>
+#include <linux/sb_config.h>
 #include "internal.h"
 
 
@@ -805,10 +806,13 @@ struct super_block *user_get_super(dev_t dev)
  *	@flags:	numeric part of options
  *	@data:	the rest of options
  *      @force: whether or not to force the change
+ *	@sc:	the superblock config for filesystems that support it
+ *		(NULL if called from emergency or umount)
  *
  *	Alters the mount options of a mounted file system.
  */
-int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
+int do_remount_sb(struct super_block *sb, int flags, void *data, int force,
+		  struct sb_config *sc)
 {
 	int retval;
 	int remount_ro;
@@ -850,8 +854,14 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
 		}
 	}
 
-	if (sb->s_op->remount_fs) {
-		retval = sb->s_op->remount_fs(sb, &flags, data);
+	if (sb->s_op->remount_fs_sc ||
+	    sb->s_op->remount_fs) {
+		if (sb->s_op->remount_fs_sc) {
+		    retval = sb->s_op->remount_fs_sc(sb, sc);
+		    flags = sc->ms_flags;
+		} else {
+			retval = sb->s_op->remount_fs(sb, &flags, data);
+		}
 		if (retval) {
 			if (!force)
 				goto cancel_readonly;
@@ -898,7 +908,7 @@ static void do_emergency_remount(struct work_struct *work)
 			/*
 			 * What lock protects sb->s_flags??
 			 */
-			do_remount_sb(sb, MS_RDONLY, NULL, 1);
+			do_remount_sb(sb, MS_RDONLY, NULL, 1, NULL);
 		}
 		up_write(&sb->s_umount);
 		spin_lock(&sb_lock);
@@ -1048,6 +1058,43 @@ struct dentry *mount_ns(struct file_system_type *fs_type,
 
 EXPORT_SYMBOL(mount_ns);
 
+int mount_ns_sc(struct sb_config *sc,
+		int (*fill_super)(struct super_block *sb, struct sb_config *sc),
+		void *ns)
+{
+	struct super_block *sb;
+
+	/* Don't allow mounting unless the caller has CAP_SYS_ADMIN
+	 * over the namespace.
+	 */
+	if (!(sc->ms_flags & MS_KERNMOUNT) &&
+	    !ns_capable(sc->user_ns, CAP_SYS_ADMIN))
+		return -EPERM;
+
+	sb = sget_userns(sc->fs_type, ns_test_super, ns_set_super,
+			 sc->ms_flags, sc->user_ns, ns);
+	if (IS_ERR(sb))
+		return PTR_ERR(sb);
+
+	if (!sb->s_root) {
+		int err;
+		err = fill_super(sb, sc);
+		if (err) {
+			deactivate_locked_super(sb);
+			return err;
+		}
+
+		sb->s_flags |= MS_ACTIVE;
+	}
+
+	if (!sc->root) {
+		sc->root = sb->s_root;
+		dget(sb->s_root);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(mount_ns_sc);
+
 #ifdef CONFIG_BLOCK
 static int set_bdev_super(struct super_block *s, void *data)
 {
@@ -1196,7 +1243,7 @@ struct dentry *mount_single(struct file_system_type *fs_type,
 		}
 		s->s_flags |= MS_ACTIVE;
 	} else {
-		do_remount_sb(s, flags, data, 0);
+		do_remount_sb(s, flags, data, 0, NULL);
 	}
 	return dget(s->s_root);
 }
diff --git a/include/linux/fs.h b/include/linux/fs.h
index bc0c054894b9..1acb76f400c4 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -54,6 +54,7 @@ struct workqueue_struct;
 struct iov_iter;
 struct fscrypt_info;
 struct fscrypt_operations;
+struct sb_config;
 
 extern void __init inode_init(void);
 extern void __init inode_init_early(void);
@@ -701,6 +702,11 @@ static inline void inode_unlock(struct inode *inode)
 	up_write(&inode->i_rwsem);
 }
 
+static inline int inode_lock_killable(struct inode *inode)
+{
+	return down_write_killable(&inode->i_rwsem);
+}
+
 static inline void inode_lock_shared(struct inode *inode)
 {
 	down_read(&inode->i_rwsem);
@@ -1787,6 +1793,7 @@ struct super_operations {
 	int (*unfreeze_fs) (struct super_block *);
 	int (*statfs) (struct dentry *, struct kstatfs *);
 	int (*remount_fs) (struct super_block *, int *, char *);
+	int (*remount_fs_sc) (struct super_block *, struct sb_config *);
 	void (*umount_begin) (struct super_block *);
 
 	int (*show_options)(struct seq_file *, struct dentry *);
@@ -2021,8 +2028,10 @@ struct file_system_type {
 #define FS_HAS_SUBTYPE		4
 #define FS_USERNS_MOUNT		8	/* Can be mounted by userns root */
 #define FS_RENAME_DOES_D_MOVE	32768	/* FS will handle d_move() during rename() internally. */
+	unsigned short sb_config_size;	/* Size of superblock config context to allocate */
 	struct dentry *(*mount) (struct file_system_type *, int,
 		       const char *, void *);
+	int (*init_sb_config)(struct sb_config *, struct super_block *);
 	void (*kill_sb) (struct super_block *);
 	struct module *owner;
 	struct file_system_type * next;
@@ -2040,6 +2049,10 @@ struct file_system_type {
 
 #define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
 
+extern int mount_ns_sc(struct sb_config *mc,
+		       int (*fill_super)(struct super_block *sb,
+					 struct sb_config *sc),
+		       void *ns);
 extern struct dentry *mount_ns(struct file_system_type *fs_type,
 	int flags, void *data, void *ns, struct user_namespace *user_ns,
 	int (*fill_super)(struct super_block *, void *, int));
@@ -2106,6 +2119,7 @@ extern int register_filesystem(struct file_system_type *);
 extern int unregister_filesystem(struct file_system_type *);
 extern struct vfsmount *kern_mount_data(struct file_system_type *, void *data);
 #define kern_mount(type) kern_mount_data(type, NULL)
+extern struct vfsmount *kern_mount_data_sc(struct sb_config *);
 extern void kern_unmount(struct vfsmount *mnt);
 extern int may_umount_tree(struct vfsmount *);
 extern int may_umount(struct vfsmount *);
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 8e0352af06b7..a5dca6abc4d5 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -20,6 +20,7 @@ struct super_block;
 struct vfsmount;
 struct dentry;
 struct mnt_namespace;
+struct sb_config;
 
 #define MNT_NOSUID	0x01
 #define MNT_NODEV	0x02
@@ -90,9 +91,12 @@ struct file_system_type;
 extern struct vfsmount *vfs_kern_mount(struct file_system_type *type,
 				      int flags, const char *name,
 				      void *data);
+extern struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc);
 extern struct vfsmount *vfs_submount(const struct dentry *mountpoint,
 				     struct file_system_type *type,
 				     const char *name, void *data);
+extern struct vfsmount *vfs_submount_sc(const struct dentry *mountpoint,
+					struct sb_config *sc);
 
 extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list);
 extern void mark_mounts_for_expiry(struct list_head *mounts);
diff --git a/include/linux/sb_config.h b/include/linux/sb_config.h
index d2af7342a082..0da92dd99d60 100644
--- a/include/linux/sb_config.h
+++ b/include/linux/sb_config.h
@@ -69,4 +69,17 @@ struct sb_config_operations {
 	int (*get_tree)(struct sb_config *sc);
 };
 
+extern struct sb_config *vfs_new_sb_config(const char *fs_name);
+extern struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
+					     struct super_block *src_sb,
+					     unsigned int ms_flags,
+					     enum sb_config_purpose purpose);
+extern struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
+					 unsigned int ms_flags);
+extern struct sb_config *vfs_dup_sb_config(struct sb_config *src);
+extern int vfs_parse_mount_option(struct sb_config *sc, char *data);
+extern int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+extern int vfs_get_tree(struct sb_config *sc);
+extern void put_sb_config(struct sb_config *sc);
+
 #endif /* _LINUX_SB_CONFIG_H */

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 10/23] VFS: Remove unused code after superblock config context changes [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (8 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 09/23] VFS: Implement a " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 11/23] VFS: Implement fsopen() to prepare for a mount " David Howells
                   ` (16 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Remove code that is now unused after the superblock config context changes.

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

 fs/internal.h             |    2 --
 fs/super.c                |   53 ---------------------------------------------
 include/linux/lsm_hooks.h |    2 --
 include/linux/security.h  |    6 -----
 security/security.c       |    5 ----
 security/selinux/hooks.c  |   20 -----------------
 6 files changed, 88 deletions(-)

diff --git a/fs/internal.h b/fs/internal.h
index 39121a99d930..de9b31568f15 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -89,8 +89,6 @@ extern struct file *get_empty_filp(void);
  */
 extern int do_remount_sb(struct super_block *, int, void *, int, struct sb_config *);
 extern bool trylock_super(struct super_block *sb);
-extern struct dentry *mount_fs(struct file_system_type *,
-			       int, const char *, void *);
 extern struct super_block *user_get_super(dev_t);
 
 /*
diff --git a/fs/super.c b/fs/super.c
index e4b47d481679..7ca217552977 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1249,59 +1249,6 @@ struct dentry *mount_single(struct file_system_type *fs_type,
 }
 EXPORT_SYMBOL(mount_single);
 
-struct dentry *
-mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
-{
-	struct dentry *root;
-	struct super_block *sb;
-	char *secdata = NULL;
-	int error = -ENOMEM;
-
-	if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
-		secdata = alloc_secdata();
-		if (!secdata)
-			goto out;
-
-		error = security_sb_copy_data(data, secdata);
-		if (error)
-			goto out_free_secdata;
-	}
-
-	root = type->mount(type, flags, name, data);
-	if (IS_ERR(root)) {
-		error = PTR_ERR(root);
-		goto out_free_secdata;
-	}
-	sb = root->d_sb;
-	BUG_ON(!sb);
-	WARN_ON(!sb->s_bdi);
-	sb->s_flags |= MS_BORN;
-
-	error = security_sb_kern_mount(sb, flags, secdata);
-	if (error)
-		goto out_sb;
-
-	/*
-	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
-	 * but s_maxbytes was an unsigned long long for many releases. Throw
-	 * this warning for a little while to try and catch filesystems that
-	 * violate this rule.
-	 */
-	WARN((sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
-		"negative value (%lld)\n", type->name, sb->s_maxbytes);
-
-	up_write(&sb->s_umount);
-	free_secdata(secdata);
-	return root;
-out_sb:
-	dput(root);
-	deactivate_locked_super(sb);
-out_free_secdata:
-	free_secdata(secdata);
-out:
-	return ERR_PTR(error);
-}
-
 /*
  * Setup private BDI for given superblock. It gets automatically cleaned up
  * in generic_shutdown_super().
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index c2bbd9e92b0a..e95a918e61ca 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1410,7 +1410,6 @@ union security_list_options {
 	void (*sb_free_security)(struct super_block *sb);
 	int (*sb_copy_data)(char *orig, char *copy);
 	int (*sb_remount)(struct super_block *sb, void *data);
-	int (*sb_kern_mount)(struct super_block *sb, int flags, void *data);
 	int (*sb_show_options)(struct seq_file *m, struct super_block *sb);
 	int (*sb_statfs)(struct dentry *dentry);
 	int (*sb_mount)(const char *dev_name, const struct path *path,
@@ -1726,7 +1725,6 @@ struct security_hook_heads {
 	struct list_head sb_free_security;
 	struct list_head sb_copy_data;
 	struct list_head sb_remount;
-	struct list_head sb_kern_mount;
 	struct list_head sb_show_options;
 	struct list_head sb_statfs;
 	struct list_head sb_mount;
diff --git a/include/linux/security.h b/include/linux/security.h
index d1dfb6abd4f7..f6eb735987d1 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -234,7 +234,6 @@ int security_sb_alloc(struct super_block *sb);
 void security_sb_free(struct super_block *sb);
 int security_sb_copy_data(char *orig, char *copy);
 int security_sb_remount(struct super_block *sb, void *data);
-int security_sb_kern_mount(struct super_block *sb, int flags, void *data);
 int security_sb_show_options(struct seq_file *m, struct super_block *sb);
 int security_sb_statfs(struct dentry *dentry);
 int security_sb_mount(const char *dev_name, const struct path *path,
@@ -566,11 +565,6 @@ static inline int security_sb_remount(struct super_block *sb, void *data)
 	return 0;
 }
 
-static inline int security_sb_kern_mount(struct super_block *sb, int flags, void *data)
-{
-	return 0;
-}
-
 static inline int security_sb_show_options(struct seq_file *m,
 					   struct super_block *sb)
 {
diff --git a/security/security.c b/security/security.c
index 951f28487719..ed086c6c8f0b 100644
--- a/security/security.c
+++ b/security/security.c
@@ -362,11 +362,6 @@ int security_sb_remount(struct super_block *sb, void *data)
 	return call_int_hook(sb_remount, 0, sb, data);
 }
 
-int security_sb_kern_mount(struct super_block *sb, int flags, void *data)
-{
-	return call_int_hook(sb_kern_mount, 0, sb, flags, data);
-}
-
 int security_sb_show_options(struct seq_file *m, struct super_block *sb)
 {
 	return call_int_hook(sb_show_options, 0, m, sb);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 420bfa955fb4..1deafdbf8716 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2775,25 +2775,6 @@ static int selinux_sb_remount(struct super_block *sb, void *data)
 	goto out_free_opts;
 }
 
-static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data)
-{
-	const struct cred *cred = current_cred();
-	struct common_audit_data ad;
-	int rc;
-
-	rc = superblock_doinit(sb, data);
-	if (rc)
-		return rc;
-
-	/* Allow all mounts performed by the kernel */
-	if (flags & MS_KERNMOUNT)
-		return 0;
-
-	ad.type = LSM_AUDIT_DATA_DENTRY;
-	ad.u.dentry = sb->s_root;
-	return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad);
-}
-
 static int selinux_sb_statfs(struct dentry *dentry)
 {
 	const struct cred *cred = current_cred();
@@ -6327,7 +6308,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
 	LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
 	LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data),
 	LSM_HOOK_INIT(sb_remount, selinux_sb_remount),
-	LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
 	LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
 	LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs),
 	LSM_HOOK_INIT(sb_mount, selinux_mount),

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 11/23] VFS: Implement fsopen() to prepare for a mount [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (9 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 10/23] VFS: Remove unused code after superblock config context changes " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 12/23] VFS: Implement fsmount() to effect a pre-configured " David Howells
                   ` (15 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Provide an fsopen() system call that starts the process of preparing to
mount, using an fd as a context handle.  fsopen() is given the name of the
filesystem that will be used:

	int mfd = fsopen(const char *fsname, int reserved,
			 int open_flags);

where reserved should be -1 for the moment (it will be used to pass the
namespace information in future) and open_flags can be 0 or O_CLOEXEC.

For example:

	mfd = fsopen("ext4", -1, O_CLOEXEC);
	write(mfd, "s /dev/sdb1"); // note I'm ignoring write's length arg
	write(mfd, "o noatime");
	write(mfd, "o acl");
	write(mfd, "o user_attr");
	write(mfd, "o iversion");
	write(mfd, "o ");
	write(mfd, "r /my/container"); // root inside the fs
	write(mfd, "x create"); // create the superblock
	fsmount(mfd, container_fd, "/mnt", AT_NO_FOLLOW);

	mfd = fsopen("afs", -1);
	write(mfd, "s %grand.central.org:root.cell");
	write(mfd, "o cell=grand.central.org");
	write(mfd, "r /");
	write(mfd, "x create");
	fsmount(mfd, AT_FDCWD, "/mnt", 0);

If an error is reported at any step, an error message may be available to be
read() back (ENODATA will be reported if there isn't an error available) in
the form:

	"e <subsys>:<problem>"
	"e SELinux:Mount on mountpoint not permitted"

Once fsmount() has been called, further write() calls will incur EBUSY,
even if the fsmount() fails.  read() is still possible to retrieve error
information.

The fsopen() syscall creates a mount context and hangs it of the fd that it
returns.

Netlink is not used because it is optional.

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

 arch/x86/entry/syscalls/syscall_32.tbl |    1 
 arch/x86/entry/syscalls/syscall_64.tbl |    1 
 fs/Makefile                            |    2 
 fs/fsopen.c                            |  267 ++++++++++++++++++++++++++++++++
 include/linux/sb_config.h              |    2 
 include/linux/syscalls.h               |    1 
 include/uapi/linux/magic.h             |    1 
 kernel/sys_ni.c                        |    3 
 8 files changed, 277 insertions(+), 1 deletion(-)
 create mode 100644 fs/fsopen.c

diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 448ac2161112..9bf8d4c62f85 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -391,3 +391,4 @@
 382	i386	pkey_free		sys_pkey_free
 383	i386	statx			sys_statx
 384	i386	arch_prctl		sys_arch_prctl			compat_sys_arch_prctl
+385	i386	fsopen			sys_fsopen
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 5aef183e2f85..9b198c5fc412 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -339,6 +339,7 @@
 330	common	pkey_alloc		sys_pkey_alloc
 331	common	pkey_free		sys_pkey_free
 332	common	statx			sys_statx
+333	common	fsopen			sys_fsopen
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Makefile b/fs/Makefile
index 8f5142525866..b8fcf48b0400 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -12,7 +12,7 @@ obj-y :=	open.o read_write.o file_table.o super.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o \
 		pnode.o splice.o sync.o utimes.o \
 		stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
-		sb_config.o
+		sb_config.o fsopen.o
 
 ifeq ($(CONFIG_BLOCK),y)
 obj-y +=	buffer.o block_dev.o direct-io.o mpage.o
diff --git a/fs/fsopen.c b/fs/fsopen.c
new file mode 100644
index 000000000000..cbede77158ba
--- /dev/null
+++ b/fs/fsopen.c
@@ -0,0 +1,267 @@
+/* Filesystem access-by-fd.
+ *
+ * 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/sb_config.h>
+#include <linux/mount.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/file.h>
+#include <linux/magic.h>
+#include <linux/syscalls.h>
+
+static struct vfsmount *fs_fs_mnt __read_mostly;
+
+static int fs_fs_release(struct inode *inode, struct file *file)
+{
+	struct sb_config *sc = file->private_data;
+
+	file->private_data = NULL;
+
+	put_sb_config(sc);
+	return 0;
+}
+
+/*
+ * Userspace writes configuration data and commands to the fd and we parse it
+ * here.  For the moment, we assume a single option or command per write.  Each
+ * line written is of the form
+ *
+ *	<option_type><space><stuff...>
+ *
+ *	d /dev/sda1				-- Device name
+ *	o noatime				-- Option without value
+ *	o cell=grand.central.org		-- Option with value
+ *	r /					-- Dir within device to mount
+ *	x create				-- Create a superblock
+ */
+static ssize_t fs_fs_write(struct file *file,
+			   const char __user *_buf, size_t len, loff_t *pos)
+{
+	struct sb_config *sc = file->private_data;
+	struct inode *inode = file_inode(file);
+	char opt[2], *data;
+	ssize_t ret;
+
+	if (len < 3 || len > 4095)
+		return -EINVAL;
+
+	if (copy_from_user(opt, _buf, 2) != 0)
+		return -EFAULT;
+	switch (opt[0]) {
+	case 's':
+	case 'o':
+	case 'x':
+		break;
+	default:
+		goto err_bad_cmd;
+	}
+	if (opt[1] != ' ')
+		goto err_bad_cmd;
+
+	data = memdup_user_nul(_buf + 2, len - 2);
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	/* From this point onwards we need to lock the fd against someone
+	 * trying to mount it.
+	 */
+	ret = inode_lock_killable(inode);
+	if (ret < 0)
+		goto err_free;
+
+	ret = -EINVAL;
+	switch (opt[0]) {
+	case 's':
+		if (sc->device)
+			goto err_unlock;
+		sc->device = data;
+		data = NULL;
+		break;
+
+	case 'o':
+		ret = vfs_parse_mount_option(sc, data);
+		if (ret < 0)
+			goto err_unlock;
+		break;
+
+	case 'x':
+		if (strcmp(data, "create") == 0) {
+			ret = vfs_get_tree(sc);
+		} else {
+			ret = invalf("VFS: Invalid command");
+		}
+		if (ret < 0)
+			goto err_unlock;
+		break;
+
+	default:
+		goto err_unlock;
+	}
+
+	ret = len;
+err_unlock:
+	inode_unlock(inode);
+err_free:
+	kfree(data);
+	return ret;
+err_bad_cmd:
+	return invalf("VFS: Unsupported write spec");
+}
+
+const struct file_operations fs_fs_fops = {
+	.write		= fs_fs_write,
+	.release	= fs_fs_release,
+	.llseek		= no_llseek,
+};
+
+/*
+ * Indicate the name we want to display the filesystem file as.
+ */
+static char *fs_fs_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+	return dynamic_dname(dentry, buffer, buflen, "fs:[%lu]",
+			     d_inode(dentry)->i_ino);
+}
+
+static const struct dentry_operations fs_fs_dentry_operations = {
+	.d_dname	= fs_fs_dname,
+};
+
+/*
+ * Create a file that can be used to configure a new mount.
+ */
+static struct file *create_fs_file(struct sb_config *sc)
+{
+	struct inode *inode;
+	struct file *f;
+	struct path path;
+	int ret;
+
+	inode = alloc_anon_inode(fs_fs_mnt->mnt_sb);
+	if (!inode)
+		return ERR_PTR(-ENFILE);
+	inode->i_fop = &fs_fs_fops;
+
+	ret = -ENOMEM;
+	path.dentry = d_alloc_pseudo(fs_fs_mnt->mnt_sb, &empty_name);
+	if (!path.dentry)
+		goto err_inode;
+	path.mnt = mntget(fs_fs_mnt);
+
+	d_instantiate(path.dentry, inode);
+
+	f = alloc_file(&path, FMODE_READ | FMODE_WRITE, &fs_fs_fops);
+	if (IS_ERR(f)) {
+		ret = PTR_ERR(f);
+		goto err_file;
+	}
+
+	f->private_data = sc;
+	return f;
+
+err_file:
+	path_put(&path);
+	return ERR_PTR(ret);
+
+err_inode:
+	iput(inode);
+	return ERR_PTR(ret);
+}
+
+ const struct super_operations fs_fs_ops = {
+	.drop_inode	= generic_delete_inode,
+	.destroy_inode	= free_inode_nonrcu,
+	.statfs		= simple_statfs,
+};
+
+static struct dentry *fs_fs_mount(struct file_system_type *fs_type,
+				  int flags, const char *dev_name,
+				  void *data)
+{
+	return mount_pseudo(fs_type, "fs_fs:", &fs_fs_ops,
+			    &fs_fs_dentry_operations, FS_FS_MAGIC);
+}
+
+static struct file_system_type fs_fs_type = {
+	.name		= "fs_fs",
+	.mount		= fs_fs_mount,
+	.kill_sb	= kill_anon_super,
+};
+
+static int __init init_fs_fs(void)
+{
+	int ret;
+
+	ret = register_filesystem(&fs_fs_type);
+	if (ret < 0)
+		panic("Cannot register fs_fs\n");
+
+	fs_fs_mnt = kern_mount(&fs_fs_type);
+	if (IS_ERR(fs_fs_mnt))
+		panic("Cannot mount fs_fs: %ld\n", PTR_ERR(fs_fs_mnt));
+	return 0;
+}
+
+fs_initcall(init_fs_fs);
+
+/*
+ * Open a filesystem by name so that it can be configured for mounting.
+ *
+ * We are allowed to specify a container in which the filesystem will be
+ * opened, thereby indicating which namespaces will be used (notably, which
+ * network namespace will be used for network filesystems).
+ */
+SYSCALL_DEFINE3(fsopen, const char __user *, _fs_name, int, reserved,
+		unsigned int, flags)
+{
+	struct sb_config *sc;
+	struct file *file;
+	const char *fs_name;
+	int fd, ret;
+
+	if (flags & ~O_CLOEXEC || reserved != -1)
+		return -EINVAL;
+
+	fs_name = strndup_user(_fs_name, PAGE_SIZE);
+	if (IS_ERR(fs_name))
+		return PTR_ERR(fs_name);
+
+	sc = vfs_new_sb_config(fs_name);
+	kfree(fs_name);
+	if (IS_ERR(sc))
+		return PTR_ERR(sc);
+
+	ret = -ENOTSUPP;
+	if (!sc->ops)
+		goto err_sc;
+
+	file = create_fs_file(sc);
+	if (IS_ERR(file)) {
+		ret = PTR_ERR(file);
+		goto err_sc;
+	}
+
+	ret = get_unused_fd_flags(flags & O_CLOEXEC);
+	if (ret < 0)
+		goto err_file;
+
+	fd = ret;
+	fd_install(fd, file);
+	return fd;
+
+err_file:
+	fput(file);
+	return ret;
+
+err_sc:
+	put_sb_config(sc);
+	return ret;
+}
diff --git a/include/linux/sb_config.h b/include/linux/sb_config.h
index 0da92dd99d60..144258d82fa1 100644
--- a/include/linux/sb_config.h
+++ b/include/linux/sb_config.h
@@ -82,4 +82,6 @@ extern int generic_monolithic_mount_data(struct sb_config *sc, void *data);
 extern int vfs_get_tree(struct sb_config *sc);
 extern void put_sb_config(struct sb_config *sc);
 
+extern const struct file_operations fs_fs_fops;
+
 #endif /* _LINUX_SB_CONFIG_H */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 980c3c9b06f8..91ec8802ad5d 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -905,5 +905,6 @@ asmlinkage long sys_pkey_alloc(unsigned long flags, unsigned long init_val);
 asmlinkage long sys_pkey_free(int pkey);
 asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
 			  unsigned mask, struct statx __user *buffer);
+asmlinkage long sys_fsopen(const char *fs_name, int containerfd, unsigned int flags);
 
 #endif
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index e230af2e6855..88ae83492f7c 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -84,5 +84,6 @@
 #define UDF_SUPER_MAGIC		0x15013346
 #define BALLOON_KVM_MAGIC	0x13661366
 #define ZSMALLOC_MAGIC		0x58295829
+#define FS_FS_MAGIC		0x66736673
 
 #endif /* __LINUX_MAGIC_H__ */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 8acef8576ce9..de1dc63e7e47 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -258,3 +258,6 @@ cond_syscall(sys_membarrier);
 cond_syscall(sys_pkey_mprotect);
 cond_syscall(sys_pkey_alloc);
 cond_syscall(sys_pkey_free);
+
+/* fd-based mount */
+cond_syscall(sys_fsopen);

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 12/23] VFS: Implement fsmount() to effect a pre-configured mount [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (10 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 11/23] VFS: Implement fsopen() to prepare for a mount " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 13/23] VFS: Add a sample program for fsopen/fsmount " David Howells
                   ` (14 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Provide a system call by which a filesystem opened with fsopen() and
configured by a series of writes can be mounted:

	int ret = fsmount(int fsfd, int dfd, const char *path,
			  unsigned int at_flags, unsigned int flags);

where fsfd is the fd returned by fsopen(), dfd, path and at_flags locate
the mountpoint and flags are the applicable MS_* flags.  dfd can be
AT_FDCWD or an fd open to a directory.

In the event that fsmount() fails, it may be possible to get an error
message by calling read().  If no message is available, ENODATA will be
reported.

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

 arch/x86/entry/syscalls/syscall_32.tbl |    1 
 arch/x86/entry/syscalls/syscall_64.tbl |    1 
 fs/namespace.c                         |   96 ++++++++++++++++++++++++++++++++
 include/linux/lsm_hooks.h              |    6 ++
 include/linux/security.h               |    5 ++
 include/linux/syscalls.h               |    2 +
 kernel/sys_ni.c                        |    1 
 security/security.c                    |    5 ++
 security/selinux/hooks.c               |   12 ++++
 9 files changed, 129 insertions(+)

diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 9bf8d4c62f85..abe6ea95e0e6 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -392,3 +392,4 @@
 383	i386	statx			sys_statx
 384	i386	arch_prctl		sys_arch_prctl			compat_sys_arch_prctl
 385	i386	fsopen			sys_fsopen
+386	i386	fsmount			sys_fsmount
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 9b198c5fc412..0977c5079831 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -340,6 +340,7 @@
 331	common	pkey_free		sys_pkey_free
 332	common	statx			sys_statx
 333	common	fsopen			sys_fsopen
+334	common	fsmount			sys_fsmount
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/namespace.c b/fs/namespace.c
index e92bc48accb5..4e9ad16db79c 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3239,6 +3239,102 @@ vfs_submount(const struct dentry *mountpoint, struct file_system_type *type,
 EXPORT_SYMBOL_GPL(vfs_submount);
 
 /*
+ * Mount a new, prepared superblock (specified by fs_fd) on the location
+ * specified by dfd and dir_name.  dfd can be AT_FDCWD, a dir fd or a container
+ * fd.  This cannot be used for binding, moving or remounting mounts.
+ */
+SYSCALL_DEFINE5(fsmount, int, fs_fd, int, dfd, const char __user *, dir_name,
+		unsigned int, at_flags, unsigned int, flags)
+{
+	struct sb_config *sc;
+	struct inode *inode;
+	struct path mountpoint;
+	struct fd f;
+	unsigned int lookup_flags, mnt_flags = 0;
+	long ret;
+
+	if ((at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
+			  AT_EMPTY_PATH)) != 0)
+		return -EINVAL;
+
+	if (flags & ~(MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC |
+		      MS_NOATIME | MS_NODIRATIME | MS_RELATIME | MS_STRICTATIME))
+		return -EINVAL;
+
+	if (flags & MS_RDONLY)
+		mnt_flags |= MNT_READONLY;
+	if (flags & MS_NOSUID)
+		mnt_flags |= MNT_NOSUID;
+	if (flags & MS_NODEV)
+		mnt_flags |= MNT_NODEV;
+	if (flags & MS_NOEXEC)
+		mnt_flags |= MNT_NOEXEC;
+	if (flags & MS_NODIRATIME)
+		mnt_flags |= MNT_NODIRATIME;
+
+	if (flags & MS_STRICTATIME) {
+		if (flags & MS_NOATIME)
+			return -EINVAL;
+	} else if (flags & MS_NOATIME) {
+		mnt_flags |= MNT_NOATIME;
+	} else {
+		mnt_flags |= MNT_RELATIME;
+	}
+
+	f = fdget(fs_fd);
+	if (!f.file)
+		return -EBADF;
+
+	ret = -EINVAL;
+	if (f.file->f_op != &fs_fs_fops)
+		goto err_fsfd;
+
+	sc = f.file->private_data;
+
+	ret = -EPERM;
+	if (!may_mount() ||
+	    ((sc->ms_flags & MS_MANDLOCK) && !may_mandlock()))
+		goto err_fsfd;
+
+	/* There must be a valid superblock or we can't mount it */
+	inode = file_inode(f.file);
+	ret = inode_lock_killable(inode);
+	if (ret == 0) {
+		if (!sc->root)
+			ret = invalf("VFS: Root must be obtained before mount");
+		inode_unlock(inode);
+	}
+	if (ret < 0)
+		goto err_fsfd;
+
+	/* Find the mountpoint.  A container can be specified in dfd. */
+	lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+	if (at_flags & AT_SYMLINK_NOFOLLOW)
+		lookup_flags &= ~LOOKUP_FOLLOW;
+	if (at_flags & AT_NO_AUTOMOUNT)
+		lookup_flags &= ~LOOKUP_AUTOMOUNT;
+	if (at_flags & AT_EMPTY_PATH)
+		lookup_flags |= LOOKUP_EMPTY;
+	ret = user_path_at(dfd, dir_name, lookup_flags, &mountpoint);
+	if (ret < 0) {
+		errorf("VFS: Mountpoint lookup failed");
+		goto err_fsfd;
+	}
+
+	ret = security_sb_mountpoint(sc, &mountpoint);
+	if (ret < 0)
+		goto err_mp;
+
+	ret = do_new_mount_sc(sc, &mountpoint, mnt_flags);
+
+err_mp:
+	path_put(&mountpoint);
+err_fsfd:
+	fdput(f);
+	return ret;
+}
+
+/*
  * Return true if path is reachable from root
  *
  * namespace_sem or mount_lock is held
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index e95a918e61ca..7064c0c15386 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -102,6 +102,10 @@
  *	@sc indicates the superblock configuration context.
  *	@sc->root indicates the root that will be mounted.
  *	@sc->root->d_sb points to the superblock.
+ * @sb_mountpoint:
+ *	Equivalent of sb_mount, but with an sb_config.
+ *	@sc indicates the superblock configuration context.
+ *	@mountpoint indicates the path on which the mount will take place.
  *
  * Security hooks for filesystem operations.
  *
@@ -1405,6 +1409,7 @@ union security_list_options {
 	void (*sb_config_free)(struct sb_config *sc);
 	int (*sb_config_parse_option)(struct sb_config *sc, char *opt);
 	int (*sb_get_tree)(struct sb_config *sc);
+	int (*sb_mountpoint)(struct sb_config *sc, struct path *mountpoint);
 
 	int (*sb_alloc_security)(struct super_block *sb);
 	void (*sb_free_security)(struct super_block *sb);
@@ -1721,6 +1726,7 @@ struct security_hook_heads {
 	struct list_head sb_config_free;
 	struct list_head sb_config_parse_option;
 	struct list_head sb_get_tree;
+	struct list_head sb_mountpoint;
 	struct list_head sb_alloc_security;
 	struct list_head sb_free_security;
 	struct list_head sb_copy_data;
diff --git a/include/linux/security.h b/include/linux/security.h
index f6eb735987d1..8c06e158c195 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -230,6 +230,7 @@ int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc);
 void security_sb_config_free(struct sb_config *sc);
 int security_sb_config_parse_option(struct sb_config *sc, char *opt);
 int security_sb_get_tree(struct sb_config *sc);
+int security_sb_mountpoint(struct sb_config *sc, struct path *mountpoint);
 int security_sb_alloc(struct super_block *sb);
 void security_sb_free(struct super_block *sb);
 int security_sb_copy_data(char *orig, char *copy);
@@ -546,6 +547,10 @@ static inline int security_sb_get_tree(struct sb_config *sc)
 {
 	return 0;
 }
+static inline int security_sb_mountpoint(struct sb_config *sc, struct path *mountpoint)
+{
+	return 0;
+}
 
 static inline int security_sb_alloc(struct super_block *sb)
 {
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 91ec8802ad5d..07e4f775f24d 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -906,5 +906,7 @@ asmlinkage long sys_pkey_free(int pkey);
 asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
 			  unsigned mask, struct statx __user *buffer);
 asmlinkage long sys_fsopen(const char *fs_name, int containerfd, unsigned int flags);
+asmlinkage long sys_fsmount(int fsfd, int dfd, const char *path, unsigned int at_flags,
+			    unsigned int flags);
 
 #endif
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index de1dc63e7e47..a0fe764bd5dd 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -261,3 +261,4 @@ cond_syscall(sys_pkey_free);
 
 /* fd-based mount */
 cond_syscall(sys_fsopen);
+cond_syscall(sys_fsmount);
diff --git a/security/security.c b/security/security.c
index ed086c6c8f0b..f4136ca5cb1b 100644
--- a/security/security.c
+++ b/security/security.c
@@ -341,6 +341,11 @@ int security_sb_get_tree(struct sb_config *sc)
 	return call_int_hook(sb_get_tree, 0, sc);
 }
 
+int security_sb_mountpoint(struct sb_config *sc, struct path *mountpoint)
+{
+	return call_int_hook(sb_mountpoint, 0, sc, mountpoint);
+}
+
 int security_sb_alloc(struct super_block *sb)
 {
 	return call_int_hook(sb_alloc_security, 0, sb);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1deafdbf8716..877b7e7bd2d5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2970,6 +2970,17 @@ static int selinux_sb_get_tree(struct sb_config *sc)
 	return rc;
 }
 
+static int selinux_sb_mountpoint(struct sb_config *sc, struct path *mountpoint)
+{
+	const struct cred *cred = current_cred();
+	int ret;
+
+	ret = path_has_perm(cred, mountpoint, FILE__MOUNTON);
+	if (ret < 0)
+		errorf("SELinux: Mount on mountpoint not permitted");
+	return ret;
+}
+
 /* inode security operations */
 
 static int selinux_inode_alloc_security(struct inode *inode)
@@ -6303,6 +6314,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
 	LSM_HOOK_INIT(sb_config_free, selinux_sb_config_free),
 	LSM_HOOK_INIT(sb_config_parse_option, selinux_sb_config_parse_option),
 	LSM_HOOK_INIT(sb_get_tree, selinux_sb_get_tree),
+	LSM_HOOK_INIT(sb_mountpoint, selinux_sb_mountpoint),
 
 	LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
 	LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 13/23] VFS: Add a sample program for fsopen/fsmount [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (11 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 12/23] VFS: Implement fsmount() to effect a pre-configured " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 14/23] procfs: Move proc_fill_super() to fs/proc/root.c " David Howells
                   ` (13 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Add a sample program for driving fsopen/fsmount.

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

 samples/fsmount/test-fsmount.c |   92 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)
 create mode 100644 samples/fsmount/test-fsmount.c

diff --git a/samples/fsmount/test-fsmount.c b/samples/fsmount/test-fsmount.c
new file mode 100644
index 000000000000..30caad8e05a3
--- /dev/null
+++ b/samples/fsmount/test-fsmount.c
@@ -0,0 +1,92 @@
+/* fd-based mount test.
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#define PR_ERRMSG_ENABLE		48
+#define PR_ERRMSG_READ			49
+
+#define E(x) do { if ((x) == -1) { perror(#x); exit(1); } } while(0)
+
+static __attribute__((noreturn))
+void mount_error(int fd, const char *s)
+{
+	char buf[4096];
+	int err, n, perr;
+
+	do {
+		err = errno;
+		errno = 0;
+		n = prctl(PR_ERRMSG_READ, buf, sizeof(buf));
+		perr = errno;
+		errno = err;
+		if (n > 0) {
+			fprintf(stderr, "Error: '%s': %*.*s: %m\n", s, n, n, buf);
+		} else {
+			fprintf(stderr, "%s: %m\n", s);
+		}
+	} while (perr == 0);
+	exit(1);
+}
+
+#define E_write(fd, s)							\
+	do {								\
+		if (write(fd, s, sizeof(s) - 1) == -1)			\
+			mount_error(fd, s);				\
+	} while (0)
+
+static inline int fsopen(const char *fs_name, int reserved, int flags)
+{
+	return syscall(333, fs_name, reserved, flags);
+}
+
+static inline int fsmount(int fsfd, int dfd, const char *path,
+			  unsigned int at_flags, unsigned int flags)
+{
+	return syscall(334, fsfd, dfd, path, at_flags, flags);
+}
+
+int main()
+{
+	int mfd;
+
+	if (prctl(PR_ERRMSG_ENABLE, 1) < 0) {
+		perror("prctl/en");
+		exit(1);
+	}
+
+	/* Mount an NFS filesystem */
+	mfd = fsopen("nfs4", -1, 0);
+	if (mfd == -1) {
+		perror("fsopen");
+		exit(1);
+	}
+
+	E_write(mfd, "s warthog:/data");
+	E_write(mfd, "o fsc");
+	E_write(mfd, "o sync");
+	E_write(mfd, "o intr");
+	E_write(mfd, "o vers=4.2");
+	E_write(mfd, "o addr=90.155.74.18");
+	E_write(mfd, "o clientaddr=90.155.74.21");
+	E_write(mfd, "x create");
+	if (fsmount(mfd, AT_FDCWD, "/mnt", 0, 0) < 0)
+		mount_error(mfd, "fsmount");
+	E(close(mfd));
+
+	exit(0);
+}

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 14/23] procfs: Move proc_fill_super() to fs/proc/root.c [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (12 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 13/23] VFS: Add a sample program for fsopen/fsmount " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 15/23] proc: Add superblock config support to procfs " David Howells
                   ` (12 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Move proc_fill_super() to fs/proc/root.c as that's where the other
superblock stuff is.

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

 fs/proc/inode.c    |   48 +-----------------------------------------------
 fs/proc/internal.h |    4 +---
 fs/proc/root.c     |   48 +++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 49 insertions(+), 51 deletions(-)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index e250910cffc8..a4bf66af0ba9 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -22,7 +22,6 @@
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/mount.h>
-#include <linux/magic.h>
 
 #include <linux/uaccess.h>
 
@@ -113,7 +112,7 @@ static int proc_show_options(struct seq_file *seq, struct dentry *root)
 	return 0;
 }
 
-static const struct super_operations proc_sops = {
+const struct super_operations proc_sops = {
 	.alloc_inode	= proc_alloc_inode,
 	.destroy_inode	= proc_destroy_inode,
 	.drop_inode	= generic_delete_inode,
@@ -470,48 +469,3 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
 	       pde_put(de);
 	return inode;
 }
-
-int proc_fill_super(struct super_block *s, void *data, int silent)
-{
-	struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
-	struct inode *root_inode;
-	int ret;
-
-	if (!proc_parse_options(data, ns))
-		return -EINVAL;
-
-	/* User space would break if executables or devices appear on proc */
-	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
-	s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
-	s->s_blocksize = 1024;
-	s->s_blocksize_bits = 10;
-	s->s_magic = PROC_SUPER_MAGIC;
-	s->s_op = &proc_sops;
-	s->s_time_gran = 1;
-
-	/*
-	 * procfs isn't actually a stacking filesystem; however, there is
-	 * too much magic going on inside it to permit stacking things on
-	 * top of it
-	 */
-	s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
-	
-	pde_get(&proc_root);
-	root_inode = proc_get_inode(s, &proc_root);
-	if (!root_inode) {
-		pr_err("proc_fill_super: get root inode failed\n");
-		return -ENOMEM;
-	}
-
-	s->s_root = d_make_root(root_inode);
-	if (!s->s_root) {
-		pr_err("proc_fill_super: allocate dentry failed\n");
-		return -ENOMEM;
-	}
-
-	ret = proc_setup_self(s);
-	if (ret) {
-		return ret;
-	}
-	return proc_setup_thread_self(s);
-}
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index c5ae09b6c726..b681533f59dd 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -197,13 +197,12 @@ struct pde_opener {
 	struct completion *c;
 };
 extern const struct inode_operations proc_link_inode_operations;
-
 extern const struct inode_operations proc_pid_link_inode_operations;
+extern const struct super_operations proc_sops;
 
 extern void proc_init_inodecache(void);
 void set_proc_pid_nlink(void);
 extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
-extern int proc_fill_super(struct super_block *, void *data, int flags);
 extern void proc_entry_rundown(struct proc_dir_entry *);
 
 /*
@@ -261,7 +260,6 @@ static inline void proc_tty_init(void) {}
  * root.c
  */
 extern struct proc_dir_entry proc_root;
-extern int proc_parse_options(char *options, struct pid_namespace *pid);
 
 extern void proc_self_init(void);
 extern int proc_remount(struct super_block *, int *, char *);
diff --git a/fs/proc/root.c b/fs/proc/root.c
index deecb397daa3..ff2e810e9e64 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -22,6 +22,7 @@
 #include <linux/pid_namespace.h>
 #include <linux/parser.h>
 #include <linux/cred.h>
+#include <linux/magic.h>
 
 #include "internal.h"
 
@@ -35,7 +36,7 @@ static const match_table_t tokens = {
 	{Opt_err, NULL},
 };
 
-int proc_parse_options(char *options, struct pid_namespace *pid)
+static int proc_parse_options(char *options, struct pid_namespace *pid)
 {
 	char *p;
 	substring_t args[MAX_OPT_ARGS];
@@ -77,6 +78,51 @@ int proc_parse_options(char *options, struct pid_namespace *pid)
 	return 1;
 }
 
+static int proc_fill_super(struct super_block *s, void *data, int silent)
+{
+	struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
+	struct inode *root_inode;
+	int ret;
+
+	if (!proc_parse_options(data, ns))
+		return -EINVAL;
+
+	/* User space would break if executables or devices appear on proc */
+	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
+	s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
+	s->s_blocksize = 1024;
+	s->s_blocksize_bits = 10;
+	s->s_magic = PROC_SUPER_MAGIC;
+	s->s_op = &proc_sops;
+	s->s_time_gran = 1;
+
+	/*
+	 * procfs isn't actually a stacking filesystem; however, there is
+	 * too much magic going on inside it to permit stacking things on
+	 * top of it
+	 */
+	s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
+	
+	pde_get(&proc_root);
+	root_inode = proc_get_inode(s, &proc_root);
+	if (!root_inode) {
+		pr_err("proc_fill_super: get root inode failed\n");
+		return -ENOMEM;
+	}
+
+	s->s_root = d_make_root(root_inode);
+	if (!s->s_root) {
+		pr_err("proc_fill_super: allocate dentry failed\n");
+		return -ENOMEM;
+	}
+
+	ret = proc_setup_self(s);
+	if (ret) {
+		return ret;
+	}
+	return proc_setup_thread_self(s);
+}
+
 int proc_remount(struct super_block *sb, int *flags, char *data)
 {
 	struct pid_namespace *pid = sb->s_fs_info;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 15/23] proc: Add superblock config support to procfs [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (13 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 14/23] procfs: Move proc_fill_super() to fs/proc/root.c " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:53 ` [PATCH 16/23] NFS: Move sb-configuration bits into their own file " David Howells
                   ` (11 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Add superblock config support to procfs.

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

 fs/proc/inode.c    |    2 -
 fs/proc/internal.h |    2 -
 fs/proc/root.c     |  175 ++++++++++++++++++++++++++++++++++------------------
 3 files changed, 116 insertions(+), 63 deletions(-)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index a4bf66af0ba9..304e5c36b7ba 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -118,7 +118,7 @@ const struct super_operations proc_sops = {
 	.drop_inode	= generic_delete_inode,
 	.evict_inode	= proc_evict_inode,
 	.statfs		= simple_statfs,
-	.remount_fs	= proc_remount,
+	.remount_fs_sc	= proc_remount,
 	.show_options	= proc_show_options,
 };
 
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index b681533f59dd..4546372c2d13 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -262,7 +262,7 @@ static inline void proc_tty_init(void) {}
 extern struct proc_dir_entry proc_root;
 
 extern void proc_self_init(void);
-extern int proc_remount(struct super_block *, int *, char *);
+extern int proc_remount(struct super_block *, struct sb_config *);
 
 /*
  * task_[no]mmu.c
diff --git a/fs/proc/root.c b/fs/proc/root.c
index ff2e810e9e64..9878b62e874c 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -18,14 +18,23 @@
 #include <linux/module.h>
 #include <linux/bitops.h>
 #include <linux/user_namespace.h>
-#include <linux/mount.h>
+#include <linux/sb_config.h>
 #include <linux/pid_namespace.h>
 #include <linux/parser.h>
 #include <linux/cred.h>
 #include <linux/magic.h>
+#include <linux/slab.h>
 
 #include "internal.h"
 
+struct proc_sb_config {
+	struct sb_config	sc;
+	struct pid_namespace	*pid_ns;
+	unsigned long		mask;
+	int			hidepid;
+	int			gid;
+};
+
 enum {
 	Opt_gid, Opt_hidepid, Opt_err,
 };
@@ -36,56 +45,60 @@ static const match_table_t tokens = {
 	{Opt_err, NULL},
 };
 
-static int proc_parse_options(char *options, struct pid_namespace *pid)
+static int proc_parse_mount_option(struct sb_config *sc, char *p)
 {
-	char *p;
+	struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
 	substring_t args[MAX_OPT_ARGS];
-	int option;
-
-	if (!options)
-		return 1;
-
-	while ((p = strsep(&options, ",")) != NULL) {
-		int token;
-		if (!*p)
-			continue;
-
-		args[0].to = args[0].from = NULL;
-		token = match_token(p, tokens, args);
-		switch (token) {
-		case Opt_gid:
-			if (match_int(&args[0], &option))
-				return 0;
-			pid->pid_gid = make_kgid(current_user_ns(), option);
-			break;
-		case Opt_hidepid:
-			if (match_int(&args[0], &option))
-				return 0;
-			if (option < HIDEPID_OFF ||
-			    option > HIDEPID_INVISIBLE) {
-				pr_err("proc: hidepid value must be between 0 and 2.\n");
-				return 0;
-			}
-			pid->hide_pid = option;
-			break;
-		default:
-			pr_err("proc: unrecognized mount option \"%s\" "
-			       "or missing value\n", p);
-			return 0;
+	int token;
+
+	args[0].to = args[0].from = NULL;
+	token = match_token(p, tokens, args);
+	switch (token) {
+	case Opt_gid:
+		if (match_int(&args[0], &cfg->gid))
+			return invalf("procfs: Unparseable gid= argument");
+		break;
+
+	case Opt_hidepid:
+		if (match_int(&args[0], &cfg->hidepid))
+			return invalf("procfs: Unparseable hidepid= argument");
+		if (cfg->hidepid < HIDEPID_OFF ||
+		    cfg->hidepid > HIDEPID_INVISIBLE) {
+			pr_err("proc: hidepid value must be between 0 and 2.\n");
+			return invalf("procfs: Invalid hidepid= argument");
 		}
+		break;
+
+	default:
+		pr_err("proc: unrecognized mount option \"%s\" "
+		       "or missing value\n", p);
+		return invalf("procfs: Invalid mount option or missing value");
 	}
 
-	return 1;
+	cfg->mask |= 1 << token;
+	return 0;
+}
+
+static void proc_set_options(struct super_block *s,
+			     struct sb_config *sc,
+			     struct pid_namespace *pid_ns,
+			     struct user_namespace *user_ns)
+{
+	struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
+
+	if (cfg->mask & (1 << Opt_gid))
+		pid_ns->pid_gid = make_kgid(user_ns, cfg->gid);
+	if (cfg->mask & (1 << Opt_hidepid))
+		pid_ns->hide_pid = cfg->hidepid;
 }
 
-static int proc_fill_super(struct super_block *s, void *data, int silent)
+static int proc_fill_super(struct super_block *s, struct sb_config *sc)
 {
-	struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
+	struct pid_namespace *pid_ns = get_pid_ns(s->s_fs_info);
 	struct inode *root_inode;
 	int ret;
 
-	if (!proc_parse_options(data, ns))
-		return -EINVAL;
+	proc_set_options(s, sc, pid_ns, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -102,7 +115,7 @@ static int proc_fill_super(struct super_block *s, void *data, int silent)
 	 * top of it
 	 */
 	s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
-	
+
 	pde_get(&proc_root);
 	root_inode = proc_get_inode(s, &proc_root);
 	if (!root_inode) {
@@ -123,27 +136,45 @@ static int proc_fill_super(struct super_block *s, void *data, int silent)
 	return proc_setup_thread_self(s);
 }
 
-int proc_remount(struct super_block *sb, int *flags, char *data)
+int proc_remount(struct super_block *sb, struct sb_config *sc)
 {
 	struct pid_namespace *pid = sb->s_fs_info;
 
 	sync_filesystem(sb);
-	return !proc_parse_options(data, pid);
+
+	if (sc)
+		proc_set_options(sb, sc, pid, current_user_ns());
+	return 0;
 }
 
-static struct dentry *proc_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *data)
+static int proc_get_tree(struct sb_config *sc)
 {
-	struct pid_namespace *ns;
+	struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
 
-	if (flags & MS_KERNMOUNT) {
-		ns = data;
-		data = NULL;
-	} else {
-		ns = task_active_pid_ns(current);
-	}
+	return mount_ns_sc(sc, proc_fill_super, cfg->pid_ns);
+}
+
+static void proc_sb_config_free(struct sb_config *sc)
+{
+	struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
 
-	return mount_ns(fs_type, flags, data, ns, ns->user_ns, proc_fill_super);
+	if (cfg->pid_ns)
+		put_pid_ns(cfg->pid_ns);
+}
+
+static const struct sb_config_operations proc_sb_config_ops = {
+	.free		= proc_sb_config_free,
+	.parse_option	= proc_parse_mount_option,
+	.get_tree	= proc_get_tree,
+};
+
+static int proc_init_sb_config(struct sb_config *sc, struct super_block *src_sb)
+{
+	struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
+
+	cfg->pid_ns = get_pid_ns(task_active_pid_ns(current));
+	cfg->sc.ops = &proc_sb_config_ops;
+	return 0;
 }
 
 static void proc_kill_sb(struct super_block *sb)
@@ -161,7 +192,8 @@ static void proc_kill_sb(struct super_block *sb)
 
 static struct file_system_type proc_fs_type = {
 	.name		= "proc",
-	.mount		= proc_mount,
+	.sb_config_size	= sizeof(struct proc_sb_config),
+	.init_sb_config	= proc_init_sb_config,
 	.kill_sb	= proc_kill_sb,
 	.fs_flags	= FS_USERNS_MOUNT,
 };
@@ -209,7 +241,7 @@ static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentr
 {
 	if (!proc_pid_lookup(dir, dentry, flags))
 		return NULL;
-	
+
 	return proc_lookup(dir, dentry, flags);
 }
 
@@ -248,12 +280,12 @@ static const struct inode_operations proc_root_inode_operations = {
  * This is the root "inode" in the /proc tree..
  */
 struct proc_dir_entry proc_root = {
-	.low_ino	= PROC_ROOT_INO, 
-	.namelen	= 5, 
-	.mode		= S_IFDIR | S_IRUGO | S_IXUGO, 
-	.nlink		= 2, 
+	.low_ino	= PROC_ROOT_INO,
+	.namelen	= 5,
+	.mode		= S_IFDIR | S_IRUGO | S_IXUGO,
+	.nlink		= 2,
 	.count		= ATOMIC_INIT(1),
-	.proc_iops	= &proc_root_inode_operations, 
+	.proc_iops	= &proc_root_inode_operations,
 	.proc_fops	= &proc_root_operations,
 	.parent		= &proc_root,
 	.subdir		= RB_ROOT,
@@ -262,9 +294,30 @@ struct proc_dir_entry proc_root = {
 
 int pid_ns_prepare_proc(struct pid_namespace *ns)
 {
+	struct proc_sb_config *cfg;
+	struct sb_config *sc;
 	struct vfsmount *mnt;
+	int ret;
+
+	sc = __vfs_new_sb_config(&proc_fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
+	if (IS_ERR(sc))
+		return PTR_ERR(sc);
+
+	cfg = container_of(sc, struct proc_sb_config, sc);
+	if (cfg->pid_ns != ns) {
+		put_pid_ns(cfg->pid_ns);
+		get_pid_ns(ns);
+		cfg->pid_ns = ns;
+	}
+
+	ret = vfs_get_tree(sc);
+	if (ret < 0) {
+		put_sb_config(sc);
+		return ret;
+	}
 
-	mnt = kern_mount_data(&proc_fs_type, ns);
+	mnt = kern_mount_data_sc(sc);
+	put_sb_config(sc);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 16/23] NFS: Move sb-configuration bits into their own file [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (14 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 15/23] proc: Add superblock config support to procfs " David Howells
@ 2017-05-22 15:53 ` David Howells
  2017-05-22 15:54 ` [PATCH 17/23] NFS: Constify mount argument match tables " David Howells
                   ` (10 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:53 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Split various bits relating to super_block configuration for mount out from
fs/nfs/super.c into their own file to form the basis of sb_config handling
for NFS.

No other changes are made to the code beyond removing 'static' qualifiers.

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

 fs/nfs/Makefile   |    2 
 fs/nfs/internal.h |   30 +
 fs/nfs/mount.c    | 1398 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfs/super.c    | 1391 -----------------------------------------------------
 4 files changed, 1429 insertions(+), 1392 deletions(-)
 create mode 100644 fs/nfs/mount.c

diff --git a/fs/nfs/Makefile b/fs/nfs/Makefile
index 98f4e5728a67..3880ef2fb69a 100644
--- a/fs/nfs/Makefile
+++ b/fs/nfs/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_NFS_FS) += nfs.o
 CFLAGS_nfstrace.o += -I$(src)
 nfs-y 			:= client.o dir.o file.o getroot.o inode.o super.o \
 			   io.o direct.o pagelist.o read.o symlink.o unlink.o \
-			   write.o namespace.o mount_clnt.o nfstrace.o
+			   write.o namespace.o mount_clnt.o nfstrace.o mount.o
 nfs-$(CONFIG_ROOT_NFS)	+= nfsroot.o
 nfs-$(CONFIG_SYSCTL)	+= sysctl.o
 nfs-$(CONFIG_NFS_FSCACHE) += fscache.o fscache-index.o
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index e9b4c3320e37..260494be399e 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -6,6 +6,7 @@
 #include <linux/mount.h>
 #include <linux/security.h>
 #include <linux/crc32.h>
+#include <linux/sunrpc/addr.h>
 #include <linux/nfs_page.h>
 
 #define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS)
@@ -229,6 +230,22 @@ extern struct svc_version nfs4_callback_version1;
 extern struct svc_version nfs4_callback_version4;
 
 struct nfs_pageio_descriptor;
+
+/* mount.c */
+#define NFS_TEXT_DATA		1
+
+extern struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void);
+extern void nfs_free_parsed_mount_data(struct nfs_parsed_mount_data *data);
+extern int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt);
+extern int nfs_validate_mount_data(struct file_system_type *fs_type,
+				   void *options,
+				   struct nfs_parsed_mount_data *args,
+				   struct nfs_fh *mntfh,
+				   const char *dev_name);
+extern int nfs_validate_text_mount_data(void *options,
+					struct nfs_parsed_mount_data *args,
+					const char *dev_name);
+
 /* pagelist.c */
 extern int __init nfs_init_nfspagecache(void);
 extern void nfs_destroy_nfspagecache(void);
@@ -768,3 +785,16 @@ static inline bool nfs_error_is_fatal(int err)
 		return false;
 	}
 }
+
+/*
+ * Select between a default port value and a user-specified port value.
+ * If a zero value is set, then autobind will be used.
+ */
+static inline void nfs_set_port(struct sockaddr *sap, int *port,
+				const unsigned short default_port)
+{
+	if (*port == NFS_UNSPEC_PORT)
+		*port = default_port;
+
+	rpc_set_port(sap, *port);
+}
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
new file mode 100644
index 000000000000..3e2fe57b9b07
--- /dev/null
+++ b/fs/nfs/mount.c
@@ -0,0 +1,1398 @@
+/* NFS mount handling.
+ *
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * Split from fs/nfs/super.c:
+ *
+ *  Copyright (C) 1992  Rick Sladkey
+ *
+ * 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/module.h>
+#include <linux/fs.h>
+#include <linux/parser.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_mount.h>
+#include <linux/nfs4_mount.h>
+#include "nfs.h"
+#include "internal.h"
+
+#define NFSDBG_FACILITY		NFSDBG_MOUNT
+
+#if IS_ENABLED(CONFIG_NFS_V3)
+#define NFS_DEFAULT_VERSION 3
+#else
+#define NFS_DEFAULT_VERSION 2
+#endif
+
+enum {
+	/* Mount options that take no arguments */
+	Opt_soft, Opt_hard,
+	Opt_posix, Opt_noposix,
+	Opt_cto, Opt_nocto,
+	Opt_ac, Opt_noac,
+	Opt_lock, Opt_nolock,
+	Opt_udp, Opt_tcp, Opt_rdma,
+	Opt_acl, Opt_noacl,
+	Opt_rdirplus, Opt_nordirplus,
+	Opt_sharecache, Opt_nosharecache,
+	Opt_resvport, Opt_noresvport,
+	Opt_fscache, Opt_nofscache,
+	Opt_migration, Opt_nomigration,
+
+	/* Mount options that take integer arguments */
+	Opt_port,
+	Opt_rsize, Opt_wsize, Opt_bsize,
+	Opt_timeo, Opt_retrans,
+	Opt_acregmin, Opt_acregmax,
+	Opt_acdirmin, Opt_acdirmax,
+	Opt_actimeo,
+	Opt_namelen,
+	Opt_mountport,
+	Opt_mountvers,
+	Opt_minorversion,
+
+	/* Mount options that take string arguments */
+	Opt_nfsvers,
+	Opt_sec, Opt_proto, Opt_mountproto, Opt_mounthost,
+	Opt_addr, Opt_mountaddr, Opt_clientaddr,
+	Opt_lookupcache,
+	Opt_fscache_uniq,
+	Opt_local_lock,
+
+	/* Special mount options */
+	Opt_userspace, Opt_deprecated, Opt_sloppy,
+
+	Opt_err
+};
+
+static const match_table_t nfs_mount_option_tokens = {
+	{ Opt_userspace, "bg" },
+	{ Opt_userspace, "fg" },
+	{ Opt_userspace, "retry=%s" },
+
+	{ Opt_sloppy, "sloppy" },
+
+	{ Opt_soft, "soft" },
+	{ Opt_hard, "hard" },
+	{ Opt_deprecated, "intr" },
+	{ Opt_deprecated, "nointr" },
+	{ Opt_posix, "posix" },
+	{ Opt_noposix, "noposix" },
+	{ Opt_cto, "cto" },
+	{ Opt_nocto, "nocto" },
+	{ Opt_ac, "ac" },
+	{ Opt_noac, "noac" },
+	{ Opt_lock, "lock" },
+	{ Opt_nolock, "nolock" },
+	{ Opt_udp, "udp" },
+	{ Opt_tcp, "tcp" },
+	{ Opt_rdma, "rdma" },
+	{ Opt_acl, "acl" },
+	{ Opt_noacl, "noacl" },
+	{ Opt_rdirplus, "rdirplus" },
+	{ Opt_nordirplus, "nordirplus" },
+	{ Opt_sharecache, "sharecache" },
+	{ Opt_nosharecache, "nosharecache" },
+	{ Opt_resvport, "resvport" },
+	{ Opt_noresvport, "noresvport" },
+	{ Opt_fscache, "fsc" },
+	{ Opt_nofscache, "nofsc" },
+	{ Opt_migration, "migration" },
+	{ Opt_nomigration, "nomigration" },
+
+	{ Opt_port, "port=%s" },
+	{ Opt_rsize, "rsize=%s" },
+	{ Opt_wsize, "wsize=%s" },
+	{ Opt_bsize, "bsize=%s" },
+	{ Opt_timeo, "timeo=%s" },
+	{ Opt_retrans, "retrans=%s" },
+	{ Opt_acregmin, "acregmin=%s" },
+	{ Opt_acregmax, "acregmax=%s" },
+	{ Opt_acdirmin, "acdirmin=%s" },
+	{ Opt_acdirmax, "acdirmax=%s" },
+	{ Opt_actimeo, "actimeo=%s" },
+	{ Opt_namelen, "namlen=%s" },
+	{ Opt_mountport, "mountport=%s" },
+	{ Opt_mountvers, "mountvers=%s" },
+	{ Opt_minorversion, "minorversion=%s" },
+
+	{ Opt_nfsvers, "nfsvers=%s" },
+	{ Opt_nfsvers, "vers=%s" },
+
+	{ Opt_sec, "sec=%s" },
+	{ Opt_proto, "proto=%s" },
+	{ Opt_mountproto, "mountproto=%s" },
+	{ Opt_addr, "addr=%s" },
+	{ Opt_clientaddr, "clientaddr=%s" },
+	{ Opt_mounthost, "mounthost=%s" },
+	{ Opt_mountaddr, "mountaddr=%s" },
+
+	{ Opt_lookupcache, "lookupcache=%s" },
+	{ Opt_fscache_uniq, "fsc=%s" },
+	{ Opt_local_lock, "local_lock=%s" },
+
+	/* The following needs to be listed after all other options */
+	{ Opt_nfsvers, "v%s" },
+
+	{ Opt_err, NULL }
+};
+
+enum {
+	Opt_xprt_udp, Opt_xprt_udp6, Opt_xprt_tcp, Opt_xprt_tcp6, Opt_xprt_rdma,
+	Opt_xprt_rdma6,
+
+	Opt_xprt_err
+};
+
+static const match_table_t nfs_xprt_protocol_tokens = {
+	{ Opt_xprt_udp, "udp" },
+	{ Opt_xprt_udp6, "udp6" },
+	{ Opt_xprt_tcp, "tcp" },
+	{ Opt_xprt_tcp6, "tcp6" },
+	{ Opt_xprt_rdma, "rdma" },
+	{ Opt_xprt_rdma6, "rdma6" },
+
+	{ Opt_xprt_err, NULL }
+};
+
+enum {
+	Opt_sec_none, Opt_sec_sys,
+	Opt_sec_krb5, Opt_sec_krb5i, Opt_sec_krb5p,
+	Opt_sec_lkey, Opt_sec_lkeyi, Opt_sec_lkeyp,
+	Opt_sec_spkm, Opt_sec_spkmi, Opt_sec_spkmp,
+
+	Opt_sec_err
+};
+
+static const match_table_t nfs_secflavor_tokens = {
+	{ Opt_sec_none, "none" },
+	{ Opt_sec_none, "null" },
+	{ Opt_sec_sys, "sys" },
+
+	{ Opt_sec_krb5, "krb5" },
+	{ Opt_sec_krb5i, "krb5i" },
+	{ Opt_sec_krb5p, "krb5p" },
+
+	{ Opt_sec_lkey, "lkey" },
+	{ Opt_sec_lkeyi, "lkeyi" },
+	{ Opt_sec_lkeyp, "lkeyp" },
+
+	{ Opt_sec_spkm, "spkm3" },
+	{ Opt_sec_spkmi, "spkm3i" },
+	{ Opt_sec_spkmp, "spkm3p" },
+
+	{ Opt_sec_err, NULL }
+};
+
+enum {
+	Opt_lookupcache_all, Opt_lookupcache_positive,
+	Opt_lookupcache_none,
+
+	Opt_lookupcache_err
+};
+
+static match_table_t nfs_lookupcache_tokens = {
+	{ Opt_lookupcache_all, "all" },
+	{ Opt_lookupcache_positive, "pos" },
+	{ Opt_lookupcache_positive, "positive" },
+	{ Opt_lookupcache_none, "none" },
+
+	{ Opt_lookupcache_err, NULL }
+};
+
+enum {
+	Opt_local_lock_all, Opt_local_lock_flock, Opt_local_lock_posix,
+	Opt_local_lock_none,
+
+	Opt_local_lock_err
+};
+
+static match_table_t nfs_local_lock_tokens = {
+	{ Opt_local_lock_all, "all" },
+	{ Opt_local_lock_flock, "flock" },
+	{ Opt_local_lock_posix, "posix" },
+	{ Opt_local_lock_none, "none" },
+
+	{ Opt_local_lock_err, NULL }
+};
+
+enum {
+	Opt_vers_2, Opt_vers_3, Opt_vers_4, Opt_vers_4_0,
+	Opt_vers_4_1, Opt_vers_4_2,
+
+	Opt_vers_err
+};
+
+static match_table_t nfs_vers_tokens = {
+	{ Opt_vers_2, "2" },
+	{ Opt_vers_3, "3" },
+	{ Opt_vers_4, "4" },
+	{ Opt_vers_4_0, "4.0" },
+	{ Opt_vers_4_1, "4.1" },
+	{ Opt_vers_4_2, "4.2" },
+
+	{ Opt_vers_err, NULL }
+};
+
+struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void)
+{
+	struct nfs_parsed_mount_data *data;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data) {
+		data->timeo		= NFS_UNSPEC_TIMEO;
+		data->retrans		= NFS_UNSPEC_RETRANS;
+		data->acregmin		= NFS_DEF_ACREGMIN;
+		data->acregmax		= NFS_DEF_ACREGMAX;
+		data->acdirmin		= NFS_DEF_ACDIRMIN;
+		data->acdirmax		= NFS_DEF_ACDIRMAX;
+		data->mount_server.port	= NFS_UNSPEC_PORT;
+		data->nfs_server.port	= NFS_UNSPEC_PORT;
+		data->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+		data->selected_flavor	= RPC_AUTH_MAXFLAVOR;
+		data->minorversion	= 0;
+		data->need_mount	= true;
+		data->net		= current->nsproxy->net_ns;
+		security_init_mnt_opts(&data->lsm_opts);
+	}
+	return data;
+}
+
+void nfs_free_parsed_mount_data(struct nfs_parsed_mount_data *data)
+{
+	if (data) {
+		kfree(data->client_address);
+		kfree(data->mount_server.hostname);
+		kfree(data->nfs_server.export_path);
+		kfree(data->nfs_server.hostname);
+		kfree(data->fscache_uniq);
+		security_free_mnt_opts(&data->lsm_opts);
+		kfree(data);
+	}
+}
+
+/*
+ * Sanity-check a server address provided by the mount command.
+ *
+ * Address family must be initialized, and address must not be
+ * the ANY address for that family.
+ */
+static int nfs_verify_server_address(struct sockaddr *addr)
+{
+	switch (addr->sa_family) {
+	case AF_INET: {
+		struct sockaddr_in *sa = (struct sockaddr_in *)addr;
+		return sa->sin_addr.s_addr != htonl(INADDR_ANY);
+	}
+	case AF_INET6: {
+		struct in6_addr *sa = &((struct sockaddr_in6 *)addr)->sin6_addr;
+		return !ipv6_addr_any(sa);
+	}
+	}
+
+	dfprintk(MOUNT, "NFS: Invalid IP address specified\n");
+	return 0;
+}
+
+/*
+ * Sanity check the NFS transport protocol.
+ *
+ */
+static void nfs_validate_transport_protocol(struct nfs_parsed_mount_data *mnt)
+{
+	switch (mnt->nfs_server.protocol) {
+	case XPRT_TRANSPORT_UDP:
+	case XPRT_TRANSPORT_TCP:
+	case XPRT_TRANSPORT_RDMA:
+		break;
+	default:
+		mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+	}
+}
+
+/*
+ * For text based NFSv2/v3 mounts, the mount protocol transport default
+ * settings should depend upon the specified NFS transport.
+ */
+static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt)
+{
+	nfs_validate_transport_protocol(mnt);
+
+	if (mnt->mount_server.protocol == XPRT_TRANSPORT_UDP ||
+	    mnt->mount_server.protocol == XPRT_TRANSPORT_TCP)
+			return;
+	switch (mnt->nfs_server.protocol) {
+	case XPRT_TRANSPORT_UDP:
+		mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
+		break;
+	case XPRT_TRANSPORT_TCP:
+	case XPRT_TRANSPORT_RDMA:
+		mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
+	}
+}
+
+/*
+ * Add 'flavor' to 'auth_info' if not already present.
+ * Returns true if 'flavor' ends up in the list, false otherwise
+ */
+static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
+			      rpc_authflavor_t flavor)
+{
+	unsigned int i;
+	unsigned int max_flavor_len = ARRAY_SIZE(auth_info->flavors);
+
+	/* make sure this flavor isn't already in the list */
+	for (i = 0; i < auth_info->flavor_len; i++) {
+		if (flavor == auth_info->flavors[i])
+			return true;
+	}
+
+	if (auth_info->flavor_len + 1 >= max_flavor_len) {
+		dfprintk(MOUNT, "NFS: too many sec= flavors\n");
+		return false;
+	}
+
+	auth_info->flavors[auth_info->flavor_len++] = flavor;
+	return true;
+}
+
+/*
+ * Parse the value of the 'sec=' option.
+ */
+static int nfs_parse_security_flavors(char *value,
+				      struct nfs_parsed_mount_data *mnt)
+{
+	substring_t args[MAX_OPT_ARGS];
+	rpc_authflavor_t pseudoflavor;
+	char *p;
+
+	dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value);
+
+	while ((p = strsep(&value, ":")) != NULL) {
+		switch (match_token(p, nfs_secflavor_tokens, args)) {
+		case Opt_sec_none:
+			pseudoflavor = RPC_AUTH_NULL;
+			break;
+		case Opt_sec_sys:
+			pseudoflavor = RPC_AUTH_UNIX;
+			break;
+		case Opt_sec_krb5:
+			pseudoflavor = RPC_AUTH_GSS_KRB5;
+			break;
+		case Opt_sec_krb5i:
+			pseudoflavor = RPC_AUTH_GSS_KRB5I;
+			break;
+		case Opt_sec_krb5p:
+			pseudoflavor = RPC_AUTH_GSS_KRB5P;
+			break;
+		case Opt_sec_lkey:
+			pseudoflavor = RPC_AUTH_GSS_LKEY;
+			break;
+		case Opt_sec_lkeyi:
+			pseudoflavor = RPC_AUTH_GSS_LKEYI;
+			break;
+		case Opt_sec_lkeyp:
+			pseudoflavor = RPC_AUTH_GSS_LKEYP;
+			break;
+		case Opt_sec_spkm:
+			pseudoflavor = RPC_AUTH_GSS_SPKM;
+			break;
+		case Opt_sec_spkmi:
+			pseudoflavor = RPC_AUTH_GSS_SPKMI;
+			break;
+		case Opt_sec_spkmp:
+			pseudoflavor = RPC_AUTH_GSS_SPKMP;
+			break;
+		default:
+			dfprintk(MOUNT,
+				 "NFS: sec= option '%s' not recognized\n", p);
+			return 0;
+		}
+
+		if (!nfs_auth_info_add(&mnt->auth_info, pseudoflavor))
+			return 0;
+	}
+
+	return 1;
+}
+
+static int nfs_parse_version_string(char *string,
+		struct nfs_parsed_mount_data *mnt,
+		substring_t *args)
+{
+	mnt->flags &= ~NFS_MOUNT_VER3;
+	switch (match_token(string, nfs_vers_tokens, args)) {
+	case Opt_vers_2:
+		mnt->version = 2;
+		break;
+	case Opt_vers_3:
+		mnt->flags |= NFS_MOUNT_VER3;
+		mnt->version = 3;
+		break;
+	case Opt_vers_4:
+		/* Backward compatibility option. In future,
+		 * the mount program should always supply
+		 * a NFSv4 minor version number.
+		 */
+		mnt->version = 4;
+		break;
+	case Opt_vers_4_0:
+		mnt->version = 4;
+		mnt->minorversion = 0;
+		break;
+	case Opt_vers_4_1:
+		mnt->version = 4;
+		mnt->minorversion = 1;
+		break;
+	case Opt_vers_4_2:
+		mnt->version = 4;
+		mnt->minorversion = 2;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int nfs_get_option_str(substring_t args[], char **option)
+{
+	kfree(*option);
+	*option = match_strdup(args);
+	return !*option;
+}
+
+static int nfs_get_option_ul(substring_t args[], unsigned long *option)
+{
+	int rc;
+	char *string;
+
+	string = match_strdup(args);
+	if (string == NULL)
+		return -ENOMEM;
+	rc = kstrtoul(string, 10, option);
+	kfree(string);
+
+	return rc;
+}
+
+static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
+		unsigned long l_bound, unsigned long u_bound)
+{
+	int ret;
+
+	ret = nfs_get_option_ul(args, option);
+	if (ret != 0)
+		return ret;
+	if (*option < l_bound || *option > u_bound)
+		return -ERANGE;
+	return 0;
+}
+
+/*
+ * Error-check and convert a string of mount options from user space into
+ * a data structure.  The whole mount string is processed; bad options are
+ * skipped as they are encountered.  If there were no errors, return 1;
+ * otherwise return 0 (zero).
+ */
+int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
+{
+	char *p, *string, *secdata;
+	int rc, sloppy = 0, invalid_option = 0;
+	unsigned short protofamily = AF_UNSPEC;
+	unsigned short mountfamily = AF_UNSPEC;
+
+	if (!raw) {
+		dfprintk(MOUNT, "NFS: mount options string was NULL.\n");
+		return 1;
+	}
+	dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw);
+
+	secdata = alloc_secdata();
+	if (!secdata)
+		goto out_nomem;
+
+	rc = security_sb_copy_data(raw, secdata);
+	if (rc)
+		goto out_security_failure;
+
+	rc = security_sb_parse_opts_str(secdata, &mnt->lsm_opts);
+	if (rc)
+		goto out_security_failure;
+
+	free_secdata(secdata);
+
+	while ((p = strsep(&raw, ",")) != NULL) {
+		substring_t args[MAX_OPT_ARGS];
+		unsigned long option;
+		int token;
+
+		if (!*p)
+			continue;
+
+		dfprintk(MOUNT, "NFS:   parsing nfs mount option '%s'\n", p);
+
+		token = match_token(p, nfs_mount_option_tokens, args);
+		switch (token) {
+
+		/*
+		 * boolean options:  foo/nofoo
+		 */
+		case Opt_soft:
+			mnt->flags |= NFS_MOUNT_SOFT;
+			break;
+		case Opt_hard:
+			mnt->flags &= ~NFS_MOUNT_SOFT;
+			break;
+		case Opt_posix:
+			mnt->flags |= NFS_MOUNT_POSIX;
+			break;
+		case Opt_noposix:
+			mnt->flags &= ~NFS_MOUNT_POSIX;
+			break;
+		case Opt_cto:
+			mnt->flags &= ~NFS_MOUNT_NOCTO;
+			break;
+		case Opt_nocto:
+			mnt->flags |= NFS_MOUNT_NOCTO;
+			break;
+		case Opt_ac:
+			mnt->flags &= ~NFS_MOUNT_NOAC;
+			break;
+		case Opt_noac:
+			mnt->flags |= NFS_MOUNT_NOAC;
+			break;
+		case Opt_lock:
+			mnt->flags &= ~NFS_MOUNT_NONLM;
+			mnt->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
+					NFS_MOUNT_LOCAL_FCNTL);
+			break;
+		case Opt_nolock:
+			mnt->flags |= NFS_MOUNT_NONLM;
+			mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |
+				       NFS_MOUNT_LOCAL_FCNTL);
+			break;
+		case Opt_udp:
+			mnt->flags &= ~NFS_MOUNT_TCP;
+			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+			break;
+		case Opt_tcp:
+			mnt->flags |= NFS_MOUNT_TCP;
+			mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+			break;
+		case Opt_rdma:
+			mnt->flags |= NFS_MOUNT_TCP; /* for side protocols */
+			mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
+			xprt_load_transport(p);
+			break;
+		case Opt_acl:
+			mnt->flags &= ~NFS_MOUNT_NOACL;
+			break;
+		case Opt_noacl:
+			mnt->flags |= NFS_MOUNT_NOACL;
+			break;
+		case Opt_rdirplus:
+			mnt->flags &= ~NFS_MOUNT_NORDIRPLUS;
+			break;
+		case Opt_nordirplus:
+			mnt->flags |= NFS_MOUNT_NORDIRPLUS;
+			break;
+		case Opt_sharecache:
+			mnt->flags &= ~NFS_MOUNT_UNSHARED;
+			break;
+		case Opt_nosharecache:
+			mnt->flags |= NFS_MOUNT_UNSHARED;
+			break;
+		case Opt_resvport:
+			mnt->flags &= ~NFS_MOUNT_NORESVPORT;
+			break;
+		case Opt_noresvport:
+			mnt->flags |= NFS_MOUNT_NORESVPORT;
+			break;
+		case Opt_fscache:
+			mnt->options |= NFS_OPTION_FSCACHE;
+			kfree(mnt->fscache_uniq);
+			mnt->fscache_uniq = NULL;
+			break;
+		case Opt_nofscache:
+			mnt->options &= ~NFS_OPTION_FSCACHE;
+			kfree(mnt->fscache_uniq);
+			mnt->fscache_uniq = NULL;
+			break;
+		case Opt_migration:
+			mnt->options |= NFS_OPTION_MIGRATION;
+			break;
+		case Opt_nomigration:
+			mnt->options &= NFS_OPTION_MIGRATION;
+			break;
+
+		/*
+		 * options that take numeric values
+		 */
+		case Opt_port:
+			if (nfs_get_option_ul(args, &option) ||
+			    option > USHRT_MAX)
+				goto out_invalid_value;
+			mnt->nfs_server.port = option;
+			break;
+		case Opt_rsize:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->rsize = option;
+			break;
+		case Opt_wsize:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->wsize = option;
+			break;
+		case Opt_bsize:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->bsize = option;
+			break;
+		case Opt_timeo:
+			if (nfs_get_option_ul_bound(args, &option, 1, INT_MAX))
+				goto out_invalid_value;
+			mnt->timeo = option;
+			break;
+		case Opt_retrans:
+			if (nfs_get_option_ul_bound(args, &option, 0, INT_MAX))
+				goto out_invalid_value;
+			mnt->retrans = option;
+			break;
+		case Opt_acregmin:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->acregmin = option;
+			break;
+		case Opt_acregmax:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->acregmax = option;
+			break;
+		case Opt_acdirmin:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->acdirmin = option;
+			break;
+		case Opt_acdirmax:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->acdirmax = option;
+			break;
+		case Opt_actimeo:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->acregmin = mnt->acregmax =
+			mnt->acdirmin = mnt->acdirmax = option;
+			break;
+		case Opt_namelen:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			mnt->namlen = option;
+			break;
+		case Opt_mountport:
+			if (nfs_get_option_ul(args, &option) ||
+			    option > USHRT_MAX)
+				goto out_invalid_value;
+			mnt->mount_server.port = option;
+			break;
+		case Opt_mountvers:
+			if (nfs_get_option_ul(args, &option) ||
+			    option < NFS_MNT_VERSION ||
+			    option > NFS_MNT3_VERSION)
+				goto out_invalid_value;
+			mnt->mount_server.version = option;
+			break;
+		case Opt_minorversion:
+			if (nfs_get_option_ul(args, &option))
+				goto out_invalid_value;
+			if (option > NFS4_MAX_MINOR_VERSION)
+				goto out_invalid_value;
+			mnt->minorversion = option;
+			break;
+
+		/*
+		 * options that take text values
+		 */
+		case Opt_nfsvers:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			rc = nfs_parse_version_string(string, mnt, args);
+			kfree(string);
+			if (!rc)
+				goto out_invalid_value;
+			break;
+		case Opt_sec:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			rc = nfs_parse_security_flavors(string, mnt);
+			kfree(string);
+			if (!rc) {
+				dfprintk(MOUNT, "NFS:   unrecognized "
+						"security flavor\n");
+				return 0;
+			}
+			break;
+		case Opt_proto:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			token = match_token(string,
+					    nfs_xprt_protocol_tokens, args);
+
+			protofamily = AF_INET;
+			switch (token) {
+			case Opt_xprt_udp6:
+				protofamily = AF_INET6;
+			case Opt_xprt_udp:
+				mnt->flags &= ~NFS_MOUNT_TCP;
+				mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+				break;
+			case Opt_xprt_tcp6:
+				protofamily = AF_INET6;
+			case Opt_xprt_tcp:
+				mnt->flags |= NFS_MOUNT_TCP;
+				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+				break;
+			case Opt_xprt_rdma6:
+				protofamily = AF_INET6;
+			case Opt_xprt_rdma:
+				/* vector side protocols to TCP */
+				mnt->flags |= NFS_MOUNT_TCP;
+				mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
+				xprt_load_transport(string);
+				break;
+			default:
+				dfprintk(MOUNT, "NFS:   unrecognized "
+						"transport protocol\n");
+				kfree(string);
+				return 0;
+			}
+			kfree(string);
+			break;
+		case Opt_mountproto:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			token = match_token(string,
+					    nfs_xprt_protocol_tokens, args);
+			kfree(string);
+
+			mountfamily = AF_INET;
+			switch (token) {
+			case Opt_xprt_udp6:
+				mountfamily = AF_INET6;
+			case Opt_xprt_udp:
+				mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
+				break;
+			case Opt_xprt_tcp6:
+				mountfamily = AF_INET6;
+			case Opt_xprt_tcp:
+				mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
+				break;
+			case Opt_xprt_rdma: /* not used for side protocols */
+			default:
+				dfprintk(MOUNT, "NFS:   unrecognized "
+						"transport protocol\n");
+				return 0;
+			}
+			break;
+		case Opt_addr:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			mnt->nfs_server.addrlen =
+				rpc_pton(mnt->net, string, strlen(string),
+					(struct sockaddr *)
+					&mnt->nfs_server.address,
+					sizeof(mnt->nfs_server.address));
+			kfree(string);
+			if (mnt->nfs_server.addrlen == 0)
+				goto out_invalid_address;
+			break;
+		case Opt_clientaddr:
+			if (nfs_get_option_str(args, &mnt->client_address))
+				goto out_nomem;
+			break;
+		case Opt_mounthost:
+			if (nfs_get_option_str(args,
+					       &mnt->mount_server.hostname))
+				goto out_nomem;
+			break;
+		case Opt_mountaddr:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			mnt->mount_server.addrlen =
+				rpc_pton(mnt->net, string, strlen(string),
+					(struct sockaddr *)
+					&mnt->mount_server.address,
+					sizeof(mnt->mount_server.address));
+			kfree(string);
+			if (mnt->mount_server.addrlen == 0)
+				goto out_invalid_address;
+			break;
+		case Opt_lookupcache:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			token = match_token(string,
+					nfs_lookupcache_tokens, args);
+			kfree(string);
+			switch (token) {
+				case Opt_lookupcache_all:
+					mnt->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
+					break;
+				case Opt_lookupcache_positive:
+					mnt->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
+					mnt->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
+					break;
+				case Opt_lookupcache_none:
+					mnt->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
+					break;
+				default:
+					dfprintk(MOUNT, "NFS:   invalid "
+							"lookupcache argument\n");
+					return 0;
+			};
+			break;
+		case Opt_fscache_uniq:
+			if (nfs_get_option_str(args, &mnt->fscache_uniq))
+				goto out_nomem;
+			mnt->options |= NFS_OPTION_FSCACHE;
+			break;
+		case Opt_local_lock:
+			string = match_strdup(args);
+			if (string == NULL)
+				goto out_nomem;
+			token = match_token(string, nfs_local_lock_tokens,
+					args);
+			kfree(string);
+			switch (token) {
+			case Opt_local_lock_all:
+				mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |
+					       NFS_MOUNT_LOCAL_FCNTL);
+				break;
+			case Opt_local_lock_flock:
+				mnt->flags |= NFS_MOUNT_LOCAL_FLOCK;
+				break;
+			case Opt_local_lock_posix:
+				mnt->flags |= NFS_MOUNT_LOCAL_FCNTL;
+				break;
+			case Opt_local_lock_none:
+				mnt->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
+						NFS_MOUNT_LOCAL_FCNTL);
+				break;
+			default:
+				dfprintk(MOUNT, "NFS:	invalid	"
+						"local_lock argument\n");
+				return 0;
+			};
+			break;
+
+		/*
+		 * Special options
+		 */
+		case Opt_sloppy:
+			sloppy = 1;
+			dfprintk(MOUNT, "NFS:   relaxing parsing rules\n");
+			break;
+		case Opt_userspace:
+		case Opt_deprecated:
+			dfprintk(MOUNT, "NFS:   ignoring mount option "
+					"'%s'\n", p);
+			break;
+
+		default:
+			invalid_option = 1;
+			dfprintk(MOUNT, "NFS:   unrecognized mount option "
+					"'%s'\n", p);
+		}
+	}
+
+	if (!sloppy && invalid_option)
+		return 0;
+
+	if (mnt->minorversion && mnt->version != 4)
+		goto out_minorversion_mismatch;
+
+	if (mnt->options & NFS_OPTION_MIGRATION &&
+	    (mnt->version != 4 || mnt->minorversion != 0))
+		goto out_migration_misuse;
+
+	/*
+	 * verify that any proto=/mountproto= options match the address
+	 * families in the addr=/mountaddr= options.
+	 */
+	if (protofamily != AF_UNSPEC &&
+	    protofamily != mnt->nfs_server.address.ss_family)
+		goto out_proto_mismatch;
+
+	if (mountfamily != AF_UNSPEC) {
+		if (mnt->mount_server.addrlen) {
+			if (mountfamily != mnt->mount_server.address.ss_family)
+				goto out_mountproto_mismatch;
+		} else {
+			if (mountfamily != mnt->nfs_server.address.ss_family)
+				goto out_mountproto_mismatch;
+		}
+	}
+
+	return 1;
+
+out_mountproto_mismatch:
+	printk(KERN_INFO "NFS: mount server address does not match mountproto= "
+			 "option\n");
+	return 0;
+out_proto_mismatch:
+	printk(KERN_INFO "NFS: server address does not match proto= option\n");
+	return 0;
+out_invalid_address:
+	printk(KERN_INFO "NFS: bad IP address specified: %s\n", p);
+	return 0;
+out_invalid_value:
+	printk(KERN_INFO "NFS: bad mount option value specified: %s\n", p);
+	return 0;
+out_minorversion_mismatch:
+	printk(KERN_INFO "NFS: mount option vers=%u does not support "
+			 "minorversion=%u\n", mnt->version, mnt->minorversion);
+	return 0;
+out_migration_misuse:
+	printk(KERN_INFO
+		"NFS: 'migration' not supported for this NFS version\n");
+	return 0;
+out_nomem:
+	printk(KERN_INFO "NFS: not enough memory to parse option\n");
+	return 0;
+out_security_failure:
+	free_secdata(secdata);
+	printk(KERN_INFO "NFS: security options invalid: %d\n", rc);
+	return 0;
+}
+
+/*
+ * Split "dev_name" into "hostname:export_path".
+ *
+ * The leftmost colon demarks the split between the server's hostname
+ * and the export path.  If the hostname starts with a left square
+ * bracket, then it may contain colons.
+ *
+ * Note: caller frees hostname and export path, even on error.
+ */
+static int nfs_parse_devname(const char *dev_name,
+			     char **hostname, size_t maxnamlen,
+			     char **export_path, size_t maxpathlen)
+{
+	size_t len;
+	char *end;
+
+	/* Is the host name protected with square brakcets? */
+	if (*dev_name == '[') {
+		end = strchr(++dev_name, ']');
+		if (end == NULL || end[1] != ':')
+			goto out_bad_devname;
+
+		len = end - dev_name;
+		end++;
+	} else {
+		char *comma;
+
+		end = strchr(dev_name, ':');
+		if (end == NULL)
+			goto out_bad_devname;
+		len = end - dev_name;
+
+		/* kill possible hostname list: not supported */
+		comma = strchr(dev_name, ',');
+		if (comma != NULL && comma < end)
+			*comma = 0;
+	}
+
+	if (len > maxnamlen)
+		goto out_hostname;
+
+	/* N.B. caller will free nfs_server.hostname in all cases */
+	*hostname = kstrndup(dev_name, len, GFP_KERNEL);
+	if (*hostname == NULL)
+		goto out_nomem;
+	len = strlen(++end);
+	if (len > maxpathlen)
+		goto out_path;
+	*export_path = kstrndup(end, len, GFP_KERNEL);
+	if (!*export_path)
+		goto out_nomem;
+
+	dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", *export_path);
+	return 0;
+
+out_bad_devname:
+	dfprintk(MOUNT, "NFS: device name not in host:path format\n");
+	return -EINVAL;
+
+out_nomem:
+	dfprintk(MOUNT, "NFS: not enough memory to parse device name\n");
+	return -ENOMEM;
+
+out_hostname:
+	dfprintk(MOUNT, "NFS: server hostname too long\n");
+	return -ENAMETOOLONG;
+
+out_path:
+	dfprintk(MOUNT, "NFS: export pathname too long\n");
+	return -ENAMETOOLONG;
+}
+
+/*
+ * Validate the NFS2/NFS3 mount data
+ * - fills in the mount root filehandle
+ *
+ * For option strings, user space handles the following behaviors:
+ *
+ * + DNS: mapping server host name to IP address ("addr=" option)
+ *
+ * + failure mode: how to behave if a mount request can't be handled
+ *   immediately ("fg/bg" option)
+ *
+ * + retry: how often to retry a mount request ("retry=" option)
+ *
+ * + breaking back: trying proto=udp after proto=tcp, v2 after v3,
+ *   mountproto=tcp after mountproto=udp, and so on
+ */
+static int nfs23_validate_mount_data(void *options,
+				     struct nfs_parsed_mount_data *args,
+				     struct nfs_fh *mntfh,
+				     const char *dev_name)
+{
+	struct nfs_mount_data *data = (struct nfs_mount_data *)options;
+	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+	int extra_flags = NFS_MOUNT_LEGACY_INTERFACE;
+
+	if (data == NULL)
+		goto out_no_data;
+
+	args->version = NFS_DEFAULT_VERSION;
+	switch (data->version) {
+	case 1:
+		data->namlen = 0;
+	case 2:
+		data->bsize = 0;
+	case 3:
+		if (data->flags & NFS_MOUNT_VER3)
+			goto out_no_v3;
+		data->root.size = NFS2_FHSIZE;
+		memcpy(data->root.data, data->old_root.data, NFS2_FHSIZE);
+		/* Turn off security negotiation */
+		extra_flags |= NFS_MOUNT_SECFLAVOUR;
+	case 4:
+		if (data->flags & NFS_MOUNT_SECFLAVOUR)
+			goto out_no_sec;
+	case 5:
+		memset(data->context, 0, sizeof(data->context));
+	case 6:
+		if (data->flags & NFS_MOUNT_VER3) {
+			if (data->root.size > NFS3_FHSIZE || data->root.size == 0)
+				goto out_invalid_fh;
+			mntfh->size = data->root.size;
+			args->version = 3;
+		} else {
+			mntfh->size = NFS2_FHSIZE;
+			args->version = 2;
+		}
+
+
+		memcpy(mntfh->data, data->root.data, mntfh->size);
+		if (mntfh->size < sizeof(mntfh->data))
+			memset(mntfh->data + mntfh->size, 0,
+			       sizeof(mntfh->data) - mntfh->size);
+
+		/*
+		 * Translate to nfs_parsed_mount_data, which nfs_fill_super
+		 * can deal with.
+		 */
+		args->flags		= data->flags & NFS_MOUNT_FLAGMASK;
+		args->flags		|= extra_flags;
+		args->rsize		= data->rsize;
+		args->wsize		= data->wsize;
+		args->timeo		= data->timeo;
+		args->retrans		= data->retrans;
+		args->acregmin		= data->acregmin;
+		args->acregmax		= data->acregmax;
+		args->acdirmin		= data->acdirmin;
+		args->acdirmax		= data->acdirmax;
+		args->need_mount	= false;
+
+		memcpy(sap, &data->addr, sizeof(data->addr));
+		args->nfs_server.addrlen = sizeof(data->addr);
+		args->nfs_server.port = ntohs(data->addr.sin_port);
+		if (!nfs_verify_server_address(sap))
+			goto out_no_address;
+
+		if (!(data->flags & NFS_MOUNT_TCP))
+			args->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+		/* N.B. caller will free nfs_server.hostname in all cases */
+		args->nfs_server.hostname = kstrdup(data->hostname, GFP_KERNEL);
+		args->namlen		= data->namlen;
+		args->bsize		= data->bsize;
+
+		if (data->flags & NFS_MOUNT_SECFLAVOUR)
+			args->selected_flavor = data->pseudoflavor;
+		else
+			args->selected_flavor = RPC_AUTH_UNIX;
+		if (!args->nfs_server.hostname)
+			goto out_nomem;
+
+		if (!(data->flags & NFS_MOUNT_NONLM))
+			args->flags &= ~(NFS_MOUNT_LOCAL_FLOCK|
+					 NFS_MOUNT_LOCAL_FCNTL);
+		else
+			args->flags |= (NFS_MOUNT_LOCAL_FLOCK|
+					NFS_MOUNT_LOCAL_FCNTL);
+		/*
+		 * The legacy version 6 binary mount data from userspace has a
+		 * field used only to transport selinux information into the
+		 * the kernel.  To continue to support that functionality we
+		 * have a touch of selinux knowledge here in the NFS code. The
+		 * userspace code converted context=blah to just blah so we are
+		 * converting back to the full string selinux understands.
+		 */
+		if (data->context[0]){
+#ifdef CONFIG_SECURITY_SELINUX
+			int rc;
+			char *opts_str = kmalloc(sizeof(data->context) + 8, GFP_KERNEL);
+			if (!opts_str)
+				return -ENOMEM;
+			strcpy(opts_str, "context=");
+			data->context[NFS_MAX_CONTEXT_LEN] = '\0';
+			strcat(opts_str, &data->context[0]);
+			rc = security_sb_parse_opts_str(opts_str, &args->lsm_opts);
+			kfree(opts_str);
+			if (rc)
+				return rc;
+#else
+			return -EINVAL;
+#endif
+		}
+
+		break;
+	default:
+		return NFS_TEXT_DATA;
+	}
+
+	return 0;
+
+out_no_data:
+	dfprintk(MOUNT, "NFS: mount program didn't pass any mount data\n");
+	return -EINVAL;
+
+out_no_v3:
+	dfprintk(MOUNT, "NFS: nfs_mount_data version %d does not support v3\n",
+		 data->version);
+	return -EINVAL;
+
+out_no_sec:
+	dfprintk(MOUNT, "NFS: nfs_mount_data version supports only AUTH_SYS\n");
+	return -EINVAL;
+
+out_nomem:
+	dfprintk(MOUNT, "NFS: not enough memory to handle mount options\n");
+	return -ENOMEM;
+
+out_no_address:
+	dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
+	return -EINVAL;
+
+out_invalid_fh:
+	dfprintk(MOUNT, "NFS: invalid root filehandle\n");
+	return -EINVAL;
+}
+
+#if IS_ENABLED(CONFIG_NFS_V4)
+
+static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)
+{
+	args->flags &= ~(NFS_MOUNT_NONLM|NFS_MOUNT_NOACL|NFS_MOUNT_VER3|
+			 NFS_MOUNT_LOCAL_FLOCK|NFS_MOUNT_LOCAL_FCNTL);
+}
+
+/*
+ * Validate NFSv4 mount options
+ */
+static int nfs4_validate_mount_data(void *options,
+				    struct nfs_parsed_mount_data *args,
+				    const char *dev_name)
+{
+	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+	struct nfs4_mount_data *data = (struct nfs4_mount_data *)options;
+	char *c;
+
+	if (data == NULL)
+		goto out_no_data;
+
+	args->version = 4;
+
+	switch (data->version) {
+	case 1:
+		if (data->host_addrlen > sizeof(args->nfs_server.address))
+			goto out_no_address;
+		if (data->host_addrlen == 0)
+			goto out_no_address;
+		args->nfs_server.addrlen = data->host_addrlen;
+		if (copy_from_user(sap, data->host_addr, data->host_addrlen))
+			return -EFAULT;
+		if (!nfs_verify_server_address(sap))
+			goto out_no_address;
+		args->nfs_server.port = ntohs(((struct sockaddr_in *)sap)->sin_port);
+
+		if (data->auth_flavourlen) {
+			rpc_authflavor_t pseudoflavor;
+			if (data->auth_flavourlen > 1)
+				goto out_inval_auth;
+			if (copy_from_user(&pseudoflavor,
+					   data->auth_flavours,
+					   sizeof(pseudoflavor)))
+				return -EFAULT;
+			args->selected_flavor = pseudoflavor;
+		} else
+			args->selected_flavor = RPC_AUTH_UNIX;
+
+		c = strndup_user(data->hostname.data, NFS4_MAXNAMLEN);
+		if (IS_ERR(c))
+			return PTR_ERR(c);
+		args->nfs_server.hostname = c;
+
+		c = strndup_user(data->mnt_path.data, NFS4_MAXPATHLEN);
+		if (IS_ERR(c))
+			return PTR_ERR(c);
+		args->nfs_server.export_path = c;
+		dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", c);
+
+		c = strndup_user(data->client_addr.data, 16);
+		if (IS_ERR(c))
+			return PTR_ERR(c);
+		args->client_address = c;
+
+		/*
+		 * Translate to nfs_parsed_mount_data, which nfs4_fill_super
+		 * can deal with.
+		 */
+
+		args->flags	= data->flags & NFS4_MOUNT_FLAGMASK;
+		args->rsize	= data->rsize;
+		args->wsize	= data->wsize;
+		args->timeo	= data->timeo;
+		args->retrans	= data->retrans;
+		args->acregmin	= data->acregmin;
+		args->acregmax	= data->acregmax;
+		args->acdirmin	= data->acdirmin;
+		args->acdirmax	= data->acdirmax;
+		args->nfs_server.protocol = data->proto;
+		nfs_validate_transport_protocol(args);
+		if (args->nfs_server.protocol == XPRT_TRANSPORT_UDP)
+			goto out_invalid_transport_udp;
+
+		break;
+	default:
+		return NFS_TEXT_DATA;
+	}
+
+	return 0;
+
+out_no_data:
+	dfprintk(MOUNT, "NFS4: mount program didn't pass any mount data\n");
+	return -EINVAL;
+
+out_inval_auth:
+	dfprintk(MOUNT, "NFS4: Invalid number of RPC auth flavours %d\n",
+		 data->auth_flavourlen);
+	return -EINVAL;
+
+out_no_address:
+	dfprintk(MOUNT, "NFS4: mount program didn't pass remote address\n");
+	return -EINVAL;
+
+out_invalid_transport_udp:
+	dfprintk(MOUNT, "NFSv4: Unsupported transport protocol udp\n");
+	return -EINVAL;
+}
+
+int nfs_validate_mount_data(struct file_system_type *fs_type,
+			    void *options,
+			    struct nfs_parsed_mount_data *args,
+			    struct nfs_fh *mntfh,
+			    const char *dev_name)
+{
+	if (fs_type == &nfs_fs_type)
+		return nfs23_validate_mount_data(options, args, mntfh, dev_name);
+	return nfs4_validate_mount_data(options, args, dev_name);
+}
+#else
+int nfs_validate_mount_data(struct file_system_type *fs_type,
+			    void *options,
+			    struct nfs_parsed_mount_data *args,
+			    struct nfs_fh *mntfh,
+			    const char *dev_name)
+{
+	return nfs23_validate_mount_data(options, args, mntfh, dev_name);
+}
+#endif
+
+int nfs_validate_text_mount_data(void *options,
+				 struct nfs_parsed_mount_data *args,
+				 const char *dev_name)
+{
+	int port = 0;
+	int max_namelen = PAGE_SIZE;
+	int max_pathlen = NFS_MAXPATHLEN;
+	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+
+	if (nfs_parse_mount_options((char *)options, args) == 0)
+		return -EINVAL;
+
+	if (!nfs_verify_server_address(sap))
+		goto out_no_address;
+
+	if (args->version == 4) {
+#if IS_ENABLED(CONFIG_NFS_V4)
+		port = NFS_PORT;
+		max_namelen = NFS4_MAXNAMLEN;
+		max_pathlen = NFS4_MAXPATHLEN;
+		nfs_validate_transport_protocol(args);
+		if (args->nfs_server.protocol == XPRT_TRANSPORT_UDP)
+			goto out_invalid_transport_udp;
+		nfs4_validate_mount_flags(args);
+#else
+		goto out_v4_not_compiled;
+#endif /* CONFIG_NFS_V4 */
+	} else
+		nfs_set_mount_transport_protocol(args);
+
+	nfs_set_port(sap, &args->nfs_server.port, port);
+
+	return nfs_parse_devname(dev_name,
+				   &args->nfs_server.hostname,
+				   max_namelen,
+				   &args->nfs_server.export_path,
+				   max_pathlen);
+
+#if !IS_ENABLED(CONFIG_NFS_V4)
+out_v4_not_compiled:
+	dfprintk(MOUNT, "NFS: NFSv4 is not compiled into kernel\n");
+	return -EPROTONOSUPPORT;
+#else
+out_invalid_transport_udp:
+	dfprintk(MOUNT, "NFSv4: Unsupported transport protocol udp\n");
+	return -EINVAL;
+#endif /* !CONFIG_NFS_V4 */
+
+out_no_address:
+	dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
+	return -EINVAL;
+}
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 2f3822a4a7d5..1f39638f4d91 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -68,223 +68,6 @@
 #include "nfs.h"
 
 #define NFSDBG_FACILITY		NFSDBG_VFS
-#define NFS_TEXT_DATA		1
-
-#if IS_ENABLED(CONFIG_NFS_V3)
-#define NFS_DEFAULT_VERSION 3
-#else
-#define NFS_DEFAULT_VERSION 2
-#endif
-
-enum {
-	/* Mount options that take no arguments */
-	Opt_soft, Opt_hard,
-	Opt_posix, Opt_noposix,
-	Opt_cto, Opt_nocto,
-	Opt_ac, Opt_noac,
-	Opt_lock, Opt_nolock,
-	Opt_udp, Opt_tcp, Opt_rdma,
-	Opt_acl, Opt_noacl,
-	Opt_rdirplus, Opt_nordirplus,
-	Opt_sharecache, Opt_nosharecache,
-	Opt_resvport, Opt_noresvport,
-	Opt_fscache, Opt_nofscache,
-	Opt_migration, Opt_nomigration,
-
-	/* Mount options that take integer arguments */
-	Opt_port,
-	Opt_rsize, Opt_wsize, Opt_bsize,
-	Opt_timeo, Opt_retrans,
-	Opt_acregmin, Opt_acregmax,
-	Opt_acdirmin, Opt_acdirmax,
-	Opt_actimeo,
-	Opt_namelen,
-	Opt_mountport,
-	Opt_mountvers,
-	Opt_minorversion,
-
-	/* Mount options that take string arguments */
-	Opt_nfsvers,
-	Opt_sec, Opt_proto, Opt_mountproto, Opt_mounthost,
-	Opt_addr, Opt_mountaddr, Opt_clientaddr,
-	Opt_lookupcache,
-	Opt_fscache_uniq,
-	Opt_local_lock,
-
-	/* Special mount options */
-	Opt_userspace, Opt_deprecated, Opt_sloppy,
-
-	Opt_err
-};
-
-static const match_table_t nfs_mount_option_tokens = {
-	{ Opt_userspace, "bg" },
-	{ Opt_userspace, "fg" },
-	{ Opt_userspace, "retry=%s" },
-
-	{ Opt_sloppy, "sloppy" },
-
-	{ Opt_soft, "soft" },
-	{ Opt_hard, "hard" },
-	{ Opt_deprecated, "intr" },
-	{ Opt_deprecated, "nointr" },
-	{ Opt_posix, "posix" },
-	{ Opt_noposix, "noposix" },
-	{ Opt_cto, "cto" },
-	{ Opt_nocto, "nocto" },
-	{ Opt_ac, "ac" },
-	{ Opt_noac, "noac" },
-	{ Opt_lock, "lock" },
-	{ Opt_nolock, "nolock" },
-	{ Opt_udp, "udp" },
-	{ Opt_tcp, "tcp" },
-	{ Opt_rdma, "rdma" },
-	{ Opt_acl, "acl" },
-	{ Opt_noacl, "noacl" },
-	{ Opt_rdirplus, "rdirplus" },
-	{ Opt_nordirplus, "nordirplus" },
-	{ Opt_sharecache, "sharecache" },
-	{ Opt_nosharecache, "nosharecache" },
-	{ Opt_resvport, "resvport" },
-	{ Opt_noresvport, "noresvport" },
-	{ Opt_fscache, "fsc" },
-	{ Opt_nofscache, "nofsc" },
-	{ Opt_migration, "migration" },
-	{ Opt_nomigration, "nomigration" },
-
-	{ Opt_port, "port=%s" },
-	{ Opt_rsize, "rsize=%s" },
-	{ Opt_wsize, "wsize=%s" },
-	{ Opt_bsize, "bsize=%s" },
-	{ Opt_timeo, "timeo=%s" },
-	{ Opt_retrans, "retrans=%s" },
-	{ Opt_acregmin, "acregmin=%s" },
-	{ Opt_acregmax, "acregmax=%s" },
-	{ Opt_acdirmin, "acdirmin=%s" },
-	{ Opt_acdirmax, "acdirmax=%s" },
-	{ Opt_actimeo, "actimeo=%s" },
-	{ Opt_namelen, "namlen=%s" },
-	{ Opt_mountport, "mountport=%s" },
-	{ Opt_mountvers, "mountvers=%s" },
-	{ Opt_minorversion, "minorversion=%s" },
-
-	{ Opt_nfsvers, "nfsvers=%s" },
-	{ Opt_nfsvers, "vers=%s" },
-
-	{ Opt_sec, "sec=%s" },
-	{ Opt_proto, "proto=%s" },
-	{ Opt_mountproto, "mountproto=%s" },
-	{ Opt_addr, "addr=%s" },
-	{ Opt_clientaddr, "clientaddr=%s" },
-	{ Opt_mounthost, "mounthost=%s" },
-	{ Opt_mountaddr, "mountaddr=%s" },
-
-	{ Opt_lookupcache, "lookupcache=%s" },
-	{ Opt_fscache_uniq, "fsc=%s" },
-	{ Opt_local_lock, "local_lock=%s" },
-
-	/* The following needs to be listed after all other options */
-	{ Opt_nfsvers, "v%s" },
-
-	{ Opt_err, NULL }
-};
-
-enum {
-	Opt_xprt_udp, Opt_xprt_udp6, Opt_xprt_tcp, Opt_xprt_tcp6, Opt_xprt_rdma,
-	Opt_xprt_rdma6,
-
-	Opt_xprt_err
-};
-
-static const match_table_t nfs_xprt_protocol_tokens = {
-	{ Opt_xprt_udp, "udp" },
-	{ Opt_xprt_udp6, "udp6" },
-	{ Opt_xprt_tcp, "tcp" },
-	{ Opt_xprt_tcp6, "tcp6" },
-	{ Opt_xprt_rdma, "rdma" },
-	{ Opt_xprt_rdma6, "rdma6" },
-
-	{ Opt_xprt_err, NULL }
-};
-
-enum {
-	Opt_sec_none, Opt_sec_sys,
-	Opt_sec_krb5, Opt_sec_krb5i, Opt_sec_krb5p,
-	Opt_sec_lkey, Opt_sec_lkeyi, Opt_sec_lkeyp,
-	Opt_sec_spkm, Opt_sec_spkmi, Opt_sec_spkmp,
-
-	Opt_sec_err
-};
-
-static const match_table_t nfs_secflavor_tokens = {
-	{ Opt_sec_none, "none" },
-	{ Opt_sec_none, "null" },
-	{ Opt_sec_sys, "sys" },
-
-	{ Opt_sec_krb5, "krb5" },
-	{ Opt_sec_krb5i, "krb5i" },
-	{ Opt_sec_krb5p, "krb5p" },
-
-	{ Opt_sec_lkey, "lkey" },
-	{ Opt_sec_lkeyi, "lkeyi" },
-	{ Opt_sec_lkeyp, "lkeyp" },
-
-	{ Opt_sec_spkm, "spkm3" },
-	{ Opt_sec_spkmi, "spkm3i" },
-	{ Opt_sec_spkmp, "spkm3p" },
-
-	{ Opt_sec_err, NULL }
-};
-
-enum {
-	Opt_lookupcache_all, Opt_lookupcache_positive,
-	Opt_lookupcache_none,
-
-	Opt_lookupcache_err
-};
-
-static match_table_t nfs_lookupcache_tokens = {
-	{ Opt_lookupcache_all, "all" },
-	{ Opt_lookupcache_positive, "pos" },
-	{ Opt_lookupcache_positive, "positive" },
-	{ Opt_lookupcache_none, "none" },
-
-	{ Opt_lookupcache_err, NULL }
-};
-
-enum {
-	Opt_local_lock_all, Opt_local_lock_flock, Opt_local_lock_posix,
-	Opt_local_lock_none,
-
-	Opt_local_lock_err
-};
-
-static match_table_t nfs_local_lock_tokens = {
-	{ Opt_local_lock_all, "all" },
-	{ Opt_local_lock_flock, "flock" },
-	{ Opt_local_lock_posix, "posix" },
-	{ Opt_local_lock_none, "none" },
-
-	{ Opt_local_lock_err, NULL }
-};
-
-enum {
-	Opt_vers_2, Opt_vers_3, Opt_vers_4, Opt_vers_4_0,
-	Opt_vers_4_1, Opt_vers_4_2,
-
-	Opt_vers_err
-};
-
-static match_table_t nfs_vers_tokens = {
-	{ Opt_vers_2, "2" },
-	{ Opt_vers_3, "3" },
-	{ Opt_vers_4, "4" },
-	{ Opt_vers_4_0, "4.0" },
-	{ Opt_vers_4_1, "4.1" },
-	{ Opt_vers_4_2, "4.2" },
-
-	{ Opt_vers_err, NULL }
-};
 
 static struct dentry *nfs_xdev_mount(struct file_system_type *fs_type,
 		int flags, const char *dev_name, void *raw_data);
@@ -324,10 +107,6 @@ const struct super_operations nfs_sops = {
 EXPORT_SYMBOL_GPL(nfs_sops);
 
 #if IS_ENABLED(CONFIG_NFS_V4)
-static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *);
-static int nfs4_validate_mount_data(void *options,
-	struct nfs_parsed_mount_data *args, const char *dev_name);
-
 struct file_system_type nfs4_fs_type = {
 	.owner		= THIS_MODULE,
 	.name		= "nfs4",
@@ -910,141 +689,6 @@ void nfs_umount_begin(struct super_block *sb)
 }
 EXPORT_SYMBOL_GPL(nfs_umount_begin);
 
-static struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void)
-{
-	struct nfs_parsed_mount_data *data;
-
-	data = kzalloc(sizeof(*data), GFP_KERNEL);
-	if (data) {
-		data->timeo		= NFS_UNSPEC_TIMEO;
-		data->retrans		= NFS_UNSPEC_RETRANS;
-		data->acregmin		= NFS_DEF_ACREGMIN;
-		data->acregmax		= NFS_DEF_ACREGMAX;
-		data->acdirmin		= NFS_DEF_ACDIRMIN;
-		data->acdirmax		= NFS_DEF_ACDIRMAX;
-		data->mount_server.port	= NFS_UNSPEC_PORT;
-		data->nfs_server.port	= NFS_UNSPEC_PORT;
-		data->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-		data->selected_flavor	= RPC_AUTH_MAXFLAVOR;
-		data->minorversion	= 0;
-		data->need_mount	= true;
-		data->net		= current->nsproxy->net_ns;
-		security_init_mnt_opts(&data->lsm_opts);
-	}
-	return data;
-}
-
-static void nfs_free_parsed_mount_data(struct nfs_parsed_mount_data *data)
-{
-	if (data) {
-		kfree(data->client_address);
-		kfree(data->mount_server.hostname);
-		kfree(data->nfs_server.export_path);
-		kfree(data->nfs_server.hostname);
-		kfree(data->fscache_uniq);
-		security_free_mnt_opts(&data->lsm_opts);
-		kfree(data);
-	}
-}
-
-/*
- * Sanity-check a server address provided by the mount command.
- *
- * Address family must be initialized, and address must not be
- * the ANY address for that family.
- */
-static int nfs_verify_server_address(struct sockaddr *addr)
-{
-	switch (addr->sa_family) {
-	case AF_INET: {
-		struct sockaddr_in *sa = (struct sockaddr_in *)addr;
-		return sa->sin_addr.s_addr != htonl(INADDR_ANY);
-	}
-	case AF_INET6: {
-		struct in6_addr *sa = &((struct sockaddr_in6 *)addr)->sin6_addr;
-		return !ipv6_addr_any(sa);
-	}
-	}
-
-	dfprintk(MOUNT, "NFS: Invalid IP address specified\n");
-	return 0;
-}
-
-/*
- * Select between a default port value and a user-specified port value.
- * If a zero value is set, then autobind will be used.
- */
-static void nfs_set_port(struct sockaddr *sap, int *port,
-				 const unsigned short default_port)
-{
-	if (*port == NFS_UNSPEC_PORT)
-		*port = default_port;
-
-	rpc_set_port(sap, *port);
-}
-
-/*
- * Sanity check the NFS transport protocol.
- *
- */
-static void nfs_validate_transport_protocol(struct nfs_parsed_mount_data *mnt)
-{
-	switch (mnt->nfs_server.protocol) {
-	case XPRT_TRANSPORT_UDP:
-	case XPRT_TRANSPORT_TCP:
-	case XPRT_TRANSPORT_RDMA:
-		break;
-	default:
-		mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-	}
-}
-
-/*
- * For text based NFSv2/v3 mounts, the mount protocol transport default
- * settings should depend upon the specified NFS transport.
- */
-static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt)
-{
-	nfs_validate_transport_protocol(mnt);
-
-	if (mnt->mount_server.protocol == XPRT_TRANSPORT_UDP ||
-	    mnt->mount_server.protocol == XPRT_TRANSPORT_TCP)
-			return;
-	switch (mnt->nfs_server.protocol) {
-	case XPRT_TRANSPORT_UDP:
-		mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
-		break;
-	case XPRT_TRANSPORT_TCP:
-	case XPRT_TRANSPORT_RDMA:
-		mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
-	}
-}
-
-/*
- * Add 'flavor' to 'auth_info' if not already present.
- * Returns true if 'flavor' ends up in the list, false otherwise
- */
-static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
-			      rpc_authflavor_t flavor)
-{
-	unsigned int i;
-	unsigned int max_flavor_len = ARRAY_SIZE(auth_info->flavors);
-
-	/* make sure this flavor isn't already in the list */
-	for (i = 0; i < auth_info->flavor_len; i++) {
-		if (flavor == auth_info->flavors[i])
-			return true;
-	}
-
-	if (auth_info->flavor_len + 1 >= max_flavor_len) {
-		dfprintk(MOUNT, "NFS: too many sec= flavors\n");
-		return false;
-	}
-
-	auth_info->flavors[auth_info->flavor_len++] = flavor;
-	return true;
-}
-
 /*
  * Return true if 'match' is in auth_info or auth_info is empty.
  * Return false otherwise.
@@ -1066,623 +710,6 @@ bool nfs_auth_info_match(const struct nfs_auth_info *auth_info,
 EXPORT_SYMBOL_GPL(nfs_auth_info_match);
 
 /*
- * Parse the value of the 'sec=' option.
- */
-static int nfs_parse_security_flavors(char *value,
-				      struct nfs_parsed_mount_data *mnt)
-{
-	substring_t args[MAX_OPT_ARGS];
-	rpc_authflavor_t pseudoflavor;
-	char *p;
-
-	dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value);
-
-	while ((p = strsep(&value, ":")) != NULL) {
-		switch (match_token(p, nfs_secflavor_tokens, args)) {
-		case Opt_sec_none:
-			pseudoflavor = RPC_AUTH_NULL;
-			break;
-		case Opt_sec_sys:
-			pseudoflavor = RPC_AUTH_UNIX;
-			break;
-		case Opt_sec_krb5:
-			pseudoflavor = RPC_AUTH_GSS_KRB5;
-			break;
-		case Opt_sec_krb5i:
-			pseudoflavor = RPC_AUTH_GSS_KRB5I;
-			break;
-		case Opt_sec_krb5p:
-			pseudoflavor = RPC_AUTH_GSS_KRB5P;
-			break;
-		case Opt_sec_lkey:
-			pseudoflavor = RPC_AUTH_GSS_LKEY;
-			break;
-		case Opt_sec_lkeyi:
-			pseudoflavor = RPC_AUTH_GSS_LKEYI;
-			break;
-		case Opt_sec_lkeyp:
-			pseudoflavor = RPC_AUTH_GSS_LKEYP;
-			break;
-		case Opt_sec_spkm:
-			pseudoflavor = RPC_AUTH_GSS_SPKM;
-			break;
-		case Opt_sec_spkmi:
-			pseudoflavor = RPC_AUTH_GSS_SPKMI;
-			break;
-		case Opt_sec_spkmp:
-			pseudoflavor = RPC_AUTH_GSS_SPKMP;
-			break;
-		default:
-			dfprintk(MOUNT,
-				 "NFS: sec= option '%s' not recognized\n", p);
-			return 0;
-		}
-
-		if (!nfs_auth_info_add(&mnt->auth_info, pseudoflavor))
-			return 0;
-	}
-
-	return 1;
-}
-
-static int nfs_parse_version_string(char *string,
-		struct nfs_parsed_mount_data *mnt,
-		substring_t *args)
-{
-	mnt->flags &= ~NFS_MOUNT_VER3;
-	switch (match_token(string, nfs_vers_tokens, args)) {
-	case Opt_vers_2:
-		mnt->version = 2;
-		break;
-	case Opt_vers_3:
-		mnt->flags |= NFS_MOUNT_VER3;
-		mnt->version = 3;
-		break;
-	case Opt_vers_4:
-		/* Backward compatibility option. In future,
-		 * the mount program should always supply
-		 * a NFSv4 minor version number.
-		 */
-		mnt->version = 4;
-		break;
-	case Opt_vers_4_0:
-		mnt->version = 4;
-		mnt->minorversion = 0;
-		break;
-	case Opt_vers_4_1:
-		mnt->version = 4;
-		mnt->minorversion = 1;
-		break;
-	case Opt_vers_4_2:
-		mnt->version = 4;
-		mnt->minorversion = 2;
-		break;
-	default:
-		return 0;
-	}
-	return 1;
-}
-
-static int nfs_get_option_str(substring_t args[], char **option)
-{
-	kfree(*option);
-	*option = match_strdup(args);
-	return !*option;
-}
-
-static int nfs_get_option_ul(substring_t args[], unsigned long *option)
-{
-	int rc;
-	char *string;
-
-	string = match_strdup(args);
-	if (string == NULL)
-		return -ENOMEM;
-	rc = kstrtoul(string, 10, option);
-	kfree(string);
-
-	return rc;
-}
-
-static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
-		unsigned long l_bound, unsigned long u_bound)
-{
-	int ret;
-
-	ret = nfs_get_option_ul(args, option);
-	if (ret != 0)
-		return ret;
-	if (*option < l_bound || *option > u_bound)
-		return -ERANGE;
-	return 0;
-}
-
-/*
- * Error-check and convert a string of mount options from user space into
- * a data structure.  The whole mount string is processed; bad options are
- * skipped as they are encountered.  If there were no errors, return 1;
- * otherwise return 0 (zero).
- */
-static int nfs_parse_mount_options(char *raw,
-				   struct nfs_parsed_mount_data *mnt)
-{
-	char *p, *string, *secdata;
-	int rc, sloppy = 0, invalid_option = 0;
-	unsigned short protofamily = AF_UNSPEC;
-	unsigned short mountfamily = AF_UNSPEC;
-
-	if (!raw) {
-		dfprintk(MOUNT, "NFS: mount options string was NULL.\n");
-		return 1;
-	}
-	dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw);
-
-	secdata = alloc_secdata();
-	if (!secdata)
-		goto out_nomem;
-
-	rc = security_sb_copy_data(raw, secdata);
-	if (rc)
-		goto out_security_failure;
-
-	rc = security_sb_parse_opts_str(secdata, &mnt->lsm_opts);
-	if (rc)
-		goto out_security_failure;
-
-	free_secdata(secdata);
-
-	while ((p = strsep(&raw, ",")) != NULL) {
-		substring_t args[MAX_OPT_ARGS];
-		unsigned long option;
-		int token;
-
-		if (!*p)
-			continue;
-
-		dfprintk(MOUNT, "NFS:   parsing nfs mount option '%s'\n", p);
-
-		token = match_token(p, nfs_mount_option_tokens, args);
-		switch (token) {
-
-		/*
-		 * boolean options:  foo/nofoo
-		 */
-		case Opt_soft:
-			mnt->flags |= NFS_MOUNT_SOFT;
-			break;
-		case Opt_hard:
-			mnt->flags &= ~NFS_MOUNT_SOFT;
-			break;
-		case Opt_posix:
-			mnt->flags |= NFS_MOUNT_POSIX;
-			break;
-		case Opt_noposix:
-			mnt->flags &= ~NFS_MOUNT_POSIX;
-			break;
-		case Opt_cto:
-			mnt->flags &= ~NFS_MOUNT_NOCTO;
-			break;
-		case Opt_nocto:
-			mnt->flags |= NFS_MOUNT_NOCTO;
-			break;
-		case Opt_ac:
-			mnt->flags &= ~NFS_MOUNT_NOAC;
-			break;
-		case Opt_noac:
-			mnt->flags |= NFS_MOUNT_NOAC;
-			break;
-		case Opt_lock:
-			mnt->flags &= ~NFS_MOUNT_NONLM;
-			mnt->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
-					NFS_MOUNT_LOCAL_FCNTL);
-			break;
-		case Opt_nolock:
-			mnt->flags |= NFS_MOUNT_NONLM;
-			mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |
-				       NFS_MOUNT_LOCAL_FCNTL);
-			break;
-		case Opt_udp:
-			mnt->flags &= ~NFS_MOUNT_TCP;
-			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-			break;
-		case Opt_tcp:
-			mnt->flags |= NFS_MOUNT_TCP;
-			mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-			break;
-		case Opt_rdma:
-			mnt->flags |= NFS_MOUNT_TCP; /* for side protocols */
-			mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
-			xprt_load_transport(p);
-			break;
-		case Opt_acl:
-			mnt->flags &= ~NFS_MOUNT_NOACL;
-			break;
-		case Opt_noacl:
-			mnt->flags |= NFS_MOUNT_NOACL;
-			break;
-		case Opt_rdirplus:
-			mnt->flags &= ~NFS_MOUNT_NORDIRPLUS;
-			break;
-		case Opt_nordirplus:
-			mnt->flags |= NFS_MOUNT_NORDIRPLUS;
-			break;
-		case Opt_sharecache:
-			mnt->flags &= ~NFS_MOUNT_UNSHARED;
-			break;
-		case Opt_nosharecache:
-			mnt->flags |= NFS_MOUNT_UNSHARED;
-			break;
-		case Opt_resvport:
-			mnt->flags &= ~NFS_MOUNT_NORESVPORT;
-			break;
-		case Opt_noresvport:
-			mnt->flags |= NFS_MOUNT_NORESVPORT;
-			break;
-		case Opt_fscache:
-			mnt->options |= NFS_OPTION_FSCACHE;
-			kfree(mnt->fscache_uniq);
-			mnt->fscache_uniq = NULL;
-			break;
-		case Opt_nofscache:
-			mnt->options &= ~NFS_OPTION_FSCACHE;
-			kfree(mnt->fscache_uniq);
-			mnt->fscache_uniq = NULL;
-			break;
-		case Opt_migration:
-			mnt->options |= NFS_OPTION_MIGRATION;
-			break;
-		case Opt_nomigration:
-			mnt->options &= NFS_OPTION_MIGRATION;
-			break;
-
-		/*
-		 * options that take numeric values
-		 */
-		case Opt_port:
-			if (nfs_get_option_ul(args, &option) ||
-			    option > USHRT_MAX)
-				goto out_invalid_value;
-			mnt->nfs_server.port = option;
-			break;
-		case Opt_rsize:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->rsize = option;
-			break;
-		case Opt_wsize:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->wsize = option;
-			break;
-		case Opt_bsize:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->bsize = option;
-			break;
-		case Opt_timeo:
-			if (nfs_get_option_ul_bound(args, &option, 1, INT_MAX))
-				goto out_invalid_value;
-			mnt->timeo = option;
-			break;
-		case Opt_retrans:
-			if (nfs_get_option_ul_bound(args, &option, 0, INT_MAX))
-				goto out_invalid_value;
-			mnt->retrans = option;
-			break;
-		case Opt_acregmin:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->acregmin = option;
-			break;
-		case Opt_acregmax:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->acregmax = option;
-			break;
-		case Opt_acdirmin:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->acdirmin = option;
-			break;
-		case Opt_acdirmax:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->acdirmax = option;
-			break;
-		case Opt_actimeo:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->acregmin = mnt->acregmax =
-			mnt->acdirmin = mnt->acdirmax = option;
-			break;
-		case Opt_namelen:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			mnt->namlen = option;
-			break;
-		case Opt_mountport:
-			if (nfs_get_option_ul(args, &option) ||
-			    option > USHRT_MAX)
-				goto out_invalid_value;
-			mnt->mount_server.port = option;
-			break;
-		case Opt_mountvers:
-			if (nfs_get_option_ul(args, &option) ||
-			    option < NFS_MNT_VERSION ||
-			    option > NFS_MNT3_VERSION)
-				goto out_invalid_value;
-			mnt->mount_server.version = option;
-			break;
-		case Opt_minorversion:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			if (option > NFS4_MAX_MINOR_VERSION)
-				goto out_invalid_value;
-			mnt->minorversion = option;
-			break;
-
-		/*
-		 * options that take text values
-		 */
-		case Opt_nfsvers:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			rc = nfs_parse_version_string(string, mnt, args);
-			kfree(string);
-			if (!rc)
-				goto out_invalid_value;
-			break;
-		case Opt_sec:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			rc = nfs_parse_security_flavors(string, mnt);
-			kfree(string);
-			if (!rc) {
-				dfprintk(MOUNT, "NFS:   unrecognized "
-						"security flavor\n");
-				return 0;
-			}
-			break;
-		case Opt_proto:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string,
-					    nfs_xprt_protocol_tokens, args);
-
-			protofamily = AF_INET;
-			switch (token) {
-			case Opt_xprt_udp6:
-				protofamily = AF_INET6;
-			case Opt_xprt_udp:
-				mnt->flags &= ~NFS_MOUNT_TCP;
-				mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-				break;
-			case Opt_xprt_tcp6:
-				protofamily = AF_INET6;
-			case Opt_xprt_tcp:
-				mnt->flags |= NFS_MOUNT_TCP;
-				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-				break;
-			case Opt_xprt_rdma6:
-				protofamily = AF_INET6;
-			case Opt_xprt_rdma:
-				/* vector side protocols to TCP */
-				mnt->flags |= NFS_MOUNT_TCP;
-				mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
-				xprt_load_transport(string);
-				break;
-			default:
-				dfprintk(MOUNT, "NFS:   unrecognized "
-						"transport protocol\n");
-				kfree(string);
-				return 0;
-			}
-			kfree(string);
-			break;
-		case Opt_mountproto:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string,
-					    nfs_xprt_protocol_tokens, args);
-			kfree(string);
-
-			mountfamily = AF_INET;
-			switch (token) {
-			case Opt_xprt_udp6:
-				mountfamily = AF_INET6;
-			case Opt_xprt_udp:
-				mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
-				break;
-			case Opt_xprt_tcp6:
-				mountfamily = AF_INET6;
-			case Opt_xprt_tcp:
-				mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
-				break;
-			case Opt_xprt_rdma: /* not used for side protocols */
-			default:
-				dfprintk(MOUNT, "NFS:   unrecognized "
-						"transport protocol\n");
-				return 0;
-			}
-			break;
-		case Opt_addr:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			mnt->nfs_server.addrlen =
-				rpc_pton(mnt->net, string, strlen(string),
-					(struct sockaddr *)
-					&mnt->nfs_server.address,
-					sizeof(mnt->nfs_server.address));
-			kfree(string);
-			if (mnt->nfs_server.addrlen == 0)
-				goto out_invalid_address;
-			break;
-		case Opt_clientaddr:
-			if (nfs_get_option_str(args, &mnt->client_address))
-				goto out_nomem;
-			break;
-		case Opt_mounthost:
-			if (nfs_get_option_str(args,
-					       &mnt->mount_server.hostname))
-				goto out_nomem;
-			break;
-		case Opt_mountaddr:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			mnt->mount_server.addrlen =
-				rpc_pton(mnt->net, string, strlen(string),
-					(struct sockaddr *)
-					&mnt->mount_server.address,
-					sizeof(mnt->mount_server.address));
-			kfree(string);
-			if (mnt->mount_server.addrlen == 0)
-				goto out_invalid_address;
-			break;
-		case Opt_lookupcache:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string,
-					nfs_lookupcache_tokens, args);
-			kfree(string);
-			switch (token) {
-				case Opt_lookupcache_all:
-					mnt->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
-					break;
-				case Opt_lookupcache_positive:
-					mnt->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
-					mnt->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
-					break;
-				case Opt_lookupcache_none:
-					mnt->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
-					break;
-				default:
-					dfprintk(MOUNT, "NFS:   invalid "
-							"lookupcache argument\n");
-					return 0;
-			};
-			break;
-		case Opt_fscache_uniq:
-			if (nfs_get_option_str(args, &mnt->fscache_uniq))
-				goto out_nomem;
-			mnt->options |= NFS_OPTION_FSCACHE;
-			break;
-		case Opt_local_lock:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string, nfs_local_lock_tokens,
-					args);
-			kfree(string);
-			switch (token) {
-			case Opt_local_lock_all:
-				mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |
-					       NFS_MOUNT_LOCAL_FCNTL);
-				break;
-			case Opt_local_lock_flock:
-				mnt->flags |= NFS_MOUNT_LOCAL_FLOCK;
-				break;
-			case Opt_local_lock_posix:
-				mnt->flags |= NFS_MOUNT_LOCAL_FCNTL;
-				break;
-			case Opt_local_lock_none:
-				mnt->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
-						NFS_MOUNT_LOCAL_FCNTL);
-				break;
-			default:
-				dfprintk(MOUNT, "NFS:	invalid	"
-						"local_lock argument\n");
-				return 0;
-			};
-			break;
-
-		/*
-		 * Special options
-		 */
-		case Opt_sloppy:
-			sloppy = 1;
-			dfprintk(MOUNT, "NFS:   relaxing parsing rules\n");
-			break;
-		case Opt_userspace:
-		case Opt_deprecated:
-			dfprintk(MOUNT, "NFS:   ignoring mount option "
-					"'%s'\n", p);
-			break;
-
-		default:
-			invalid_option = 1;
-			dfprintk(MOUNT, "NFS:   unrecognized mount option "
-					"'%s'\n", p);
-		}
-	}
-
-	if (!sloppy && invalid_option)
-		return 0;
-
-	if (mnt->minorversion && mnt->version != 4)
-		goto out_minorversion_mismatch;
-
-	if (mnt->options & NFS_OPTION_MIGRATION &&
-	    (mnt->version != 4 || mnt->minorversion != 0))
-		goto out_migration_misuse;
-
-	/*
-	 * verify that any proto=/mountproto= options match the address
-	 * families in the addr=/mountaddr= options.
-	 */
-	if (protofamily != AF_UNSPEC &&
-	    protofamily != mnt->nfs_server.address.ss_family)
-		goto out_proto_mismatch;
-
-	if (mountfamily != AF_UNSPEC) {
-		if (mnt->mount_server.addrlen) {
-			if (mountfamily != mnt->mount_server.address.ss_family)
-				goto out_mountproto_mismatch;
-		} else {
-			if (mountfamily != mnt->nfs_server.address.ss_family)
-				goto out_mountproto_mismatch;
-		}
-	}
-
-	return 1;
-
-out_mountproto_mismatch:
-	printk(KERN_INFO "NFS: mount server address does not match mountproto= "
-			 "option\n");
-	return 0;
-out_proto_mismatch:
-	printk(KERN_INFO "NFS: server address does not match proto= option\n");
-	return 0;
-out_invalid_address:
-	printk(KERN_INFO "NFS: bad IP address specified: %s\n", p);
-	return 0;
-out_invalid_value:
-	printk(KERN_INFO "NFS: bad mount option value specified: %s\n", p);
-	return 0;
-out_minorversion_mismatch:
-	printk(KERN_INFO "NFS: mount option vers=%u does not support "
-			 "minorversion=%u\n", mnt->version, mnt->minorversion);
-	return 0;
-out_migration_misuse:
-	printk(KERN_INFO
-		"NFS: 'migration' not supported for this NFS version\n");
-	return 0;
-out_nomem:
-	printk(KERN_INFO "NFS: not enough memory to parse option\n");
-	return 0;
-out_security_failure:
-	free_secdata(secdata);
-	printk(KERN_INFO "NFS: security options invalid: %d\n", rc);
-	return 0;
-}
-
-/*
  * Ensure that a specified authtype in args->auth_info is supported by
  * the server. Returns 0 and sets args->selected_flavor if it's ok, and
  * -EACCES if not.
@@ -1881,317 +908,6 @@ struct dentry *nfs_try_mount(int flags, const char *dev_name,
 }
 EXPORT_SYMBOL_GPL(nfs_try_mount);
 
-/*
- * Split "dev_name" into "hostname:export_path".
- *
- * The leftmost colon demarks the split between the server's hostname
- * and the export path.  If the hostname starts with a left square
- * bracket, then it may contain colons.
- *
- * Note: caller frees hostname and export path, even on error.
- */
-static int nfs_parse_devname(const char *dev_name,
-			     char **hostname, size_t maxnamlen,
-			     char **export_path, size_t maxpathlen)
-{
-	size_t len;
-	char *end;
-
-	/* Is the host name protected with square brakcets? */
-	if (*dev_name == '[') {
-		end = strchr(++dev_name, ']');
-		if (end == NULL || end[1] != ':')
-			goto out_bad_devname;
-
-		len = end - dev_name;
-		end++;
-	} else {
-		char *comma;
-
-		end = strchr(dev_name, ':');
-		if (end == NULL)
-			goto out_bad_devname;
-		len = end - dev_name;
-
-		/* kill possible hostname list: not supported */
-		comma = strchr(dev_name, ',');
-		if (comma != NULL && comma < end)
-			*comma = 0;
-	}
-
-	if (len > maxnamlen)
-		goto out_hostname;
-
-	/* N.B. caller will free nfs_server.hostname in all cases */
-	*hostname = kstrndup(dev_name, len, GFP_KERNEL);
-	if (*hostname == NULL)
-		goto out_nomem;
-	len = strlen(++end);
-	if (len > maxpathlen)
-		goto out_path;
-	*export_path = kstrndup(end, len, GFP_KERNEL);
-	if (!*export_path)
-		goto out_nomem;
-
-	dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", *export_path);
-	return 0;
-
-out_bad_devname:
-	dfprintk(MOUNT, "NFS: device name not in host:path format\n");
-	return -EINVAL;
-
-out_nomem:
-	dfprintk(MOUNT, "NFS: not enough memory to parse device name\n");
-	return -ENOMEM;
-
-out_hostname:
-	dfprintk(MOUNT, "NFS: server hostname too long\n");
-	return -ENAMETOOLONG;
-
-out_path:
-	dfprintk(MOUNT, "NFS: export pathname too long\n");
-	return -ENAMETOOLONG;
-}
-
-/*
- * Validate the NFS2/NFS3 mount data
- * - fills in the mount root filehandle
- *
- * For option strings, user space handles the following behaviors:
- *
- * + DNS: mapping server host name to IP address ("addr=" option)
- *
- * + failure mode: how to behave if a mount request can't be handled
- *   immediately ("fg/bg" option)
- *
- * + retry: how often to retry a mount request ("retry=" option)
- *
- * + breaking back: trying proto=udp after proto=tcp, v2 after v3,
- *   mountproto=tcp after mountproto=udp, and so on
- */
-static int nfs23_validate_mount_data(void *options,
-				     struct nfs_parsed_mount_data *args,
-				     struct nfs_fh *mntfh,
-				     const char *dev_name)
-{
-	struct nfs_mount_data *data = (struct nfs_mount_data *)options;
-	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
-	int extra_flags = NFS_MOUNT_LEGACY_INTERFACE;
-
-	if (data == NULL)
-		goto out_no_data;
-
-	args->version = NFS_DEFAULT_VERSION;
-	switch (data->version) {
-	case 1:
-		data->namlen = 0;
-	case 2:
-		data->bsize = 0;
-	case 3:
-		if (data->flags & NFS_MOUNT_VER3)
-			goto out_no_v3;
-		data->root.size = NFS2_FHSIZE;
-		memcpy(data->root.data, data->old_root.data, NFS2_FHSIZE);
-		/* Turn off security negotiation */
-		extra_flags |= NFS_MOUNT_SECFLAVOUR;
-	case 4:
-		if (data->flags & NFS_MOUNT_SECFLAVOUR)
-			goto out_no_sec;
-	case 5:
-		memset(data->context, 0, sizeof(data->context));
-	case 6:
-		if (data->flags & NFS_MOUNT_VER3) {
-			if (data->root.size > NFS3_FHSIZE || data->root.size == 0)
-				goto out_invalid_fh;
-			mntfh->size = data->root.size;
-			args->version = 3;
-		} else {
-			mntfh->size = NFS2_FHSIZE;
-			args->version = 2;
-		}
-
-
-		memcpy(mntfh->data, data->root.data, mntfh->size);
-		if (mntfh->size < sizeof(mntfh->data))
-			memset(mntfh->data + mntfh->size, 0,
-			       sizeof(mntfh->data) - mntfh->size);
-
-		/*
-		 * Translate to nfs_parsed_mount_data, which nfs_fill_super
-		 * can deal with.
-		 */
-		args->flags		= data->flags & NFS_MOUNT_FLAGMASK;
-		args->flags		|= extra_flags;
-		args->rsize		= data->rsize;
-		args->wsize		= data->wsize;
-		args->timeo		= data->timeo;
-		args->retrans		= data->retrans;
-		args->acregmin		= data->acregmin;
-		args->acregmax		= data->acregmax;
-		args->acdirmin		= data->acdirmin;
-		args->acdirmax		= data->acdirmax;
-		args->need_mount	= false;
-
-		memcpy(sap, &data->addr, sizeof(data->addr));
-		args->nfs_server.addrlen = sizeof(data->addr);
-		args->nfs_server.port = ntohs(data->addr.sin_port);
-		if (!nfs_verify_server_address(sap))
-			goto out_no_address;
-
-		if (!(data->flags & NFS_MOUNT_TCP))
-			args->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-		/* N.B. caller will free nfs_server.hostname in all cases */
-		args->nfs_server.hostname = kstrdup(data->hostname, GFP_KERNEL);
-		args->namlen		= data->namlen;
-		args->bsize		= data->bsize;
-
-		if (data->flags & NFS_MOUNT_SECFLAVOUR)
-			args->selected_flavor = data->pseudoflavor;
-		else
-			args->selected_flavor = RPC_AUTH_UNIX;
-		if (!args->nfs_server.hostname)
-			goto out_nomem;
-
-		if (!(data->flags & NFS_MOUNT_NONLM))
-			args->flags &= ~(NFS_MOUNT_LOCAL_FLOCK|
-					 NFS_MOUNT_LOCAL_FCNTL);
-		else
-			args->flags |= (NFS_MOUNT_LOCAL_FLOCK|
-					NFS_MOUNT_LOCAL_FCNTL);
-		/*
-		 * The legacy version 6 binary mount data from userspace has a
-		 * field used only to transport selinux information into the
-		 * the kernel.  To continue to support that functionality we
-		 * have a touch of selinux knowledge here in the NFS code. The
-		 * userspace code converted context=blah to just blah so we are
-		 * converting back to the full string selinux understands.
-		 */
-		if (data->context[0]){
-#ifdef CONFIG_SECURITY_SELINUX
-			int rc;
-			char *opts_str = kmalloc(sizeof(data->context) + 8, GFP_KERNEL);
-			if (!opts_str)
-				return -ENOMEM;
-			strcpy(opts_str, "context=");
-			data->context[NFS_MAX_CONTEXT_LEN] = '\0';
-			strcat(opts_str, &data->context[0]);
-			rc = security_sb_parse_opts_str(opts_str, &args->lsm_opts);
-			kfree(opts_str);
-			if (rc)
-				return rc;
-#else
-			return -EINVAL;
-#endif
-		}
-
-		break;
-	default:
-		return NFS_TEXT_DATA;
-	}
-
-	return 0;
-
-out_no_data:
-	dfprintk(MOUNT, "NFS: mount program didn't pass any mount data\n");
-	return -EINVAL;
-
-out_no_v3:
-	dfprintk(MOUNT, "NFS: nfs_mount_data version %d does not support v3\n",
-		 data->version);
-	return -EINVAL;
-
-out_no_sec:
-	dfprintk(MOUNT, "NFS: nfs_mount_data version supports only AUTH_SYS\n");
-	return -EINVAL;
-
-out_nomem:
-	dfprintk(MOUNT, "NFS: not enough memory to handle mount options\n");
-	return -ENOMEM;
-
-out_no_address:
-	dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
-	return -EINVAL;
-
-out_invalid_fh:
-	dfprintk(MOUNT, "NFS: invalid root filehandle\n");
-	return -EINVAL;
-}
-
-#if IS_ENABLED(CONFIG_NFS_V4)
-static int nfs_validate_mount_data(struct file_system_type *fs_type,
-				   void *options,
-				   struct nfs_parsed_mount_data *args,
-				   struct nfs_fh *mntfh,
-				   const char *dev_name)
-{
-	if (fs_type == &nfs_fs_type)
-		return nfs23_validate_mount_data(options, args, mntfh, dev_name);
-	return nfs4_validate_mount_data(options, args, dev_name);
-}
-#else
-static int nfs_validate_mount_data(struct file_system_type *fs_type,
-				   void *options,
-				   struct nfs_parsed_mount_data *args,
-				   struct nfs_fh *mntfh,
-				   const char *dev_name)
-{
-	return nfs23_validate_mount_data(options, args, mntfh, dev_name);
-}
-#endif
-
-static int nfs_validate_text_mount_data(void *options,
-					struct nfs_parsed_mount_data *args,
-					const char *dev_name)
-{
-	int port = 0;
-	int max_namelen = PAGE_SIZE;
-	int max_pathlen = NFS_MAXPATHLEN;
-	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
-
-	if (nfs_parse_mount_options((char *)options, args) == 0)
-		return -EINVAL;
-
-	if (!nfs_verify_server_address(sap))
-		goto out_no_address;
-
-	if (args->version == 4) {
-#if IS_ENABLED(CONFIG_NFS_V4)
-		port = NFS_PORT;
-		max_namelen = NFS4_MAXNAMLEN;
-		max_pathlen = NFS4_MAXPATHLEN;
-		nfs_validate_transport_protocol(args);
-		if (args->nfs_server.protocol == XPRT_TRANSPORT_UDP)
-			goto out_invalid_transport_udp;
-		nfs4_validate_mount_flags(args);
-#else
-		goto out_v4_not_compiled;
-#endif /* CONFIG_NFS_V4 */
-	} else
-		nfs_set_mount_transport_protocol(args);
-
-	nfs_set_port(sap, &args->nfs_server.port, port);
-
-	return nfs_parse_devname(dev_name,
-				   &args->nfs_server.hostname,
-				   max_namelen,
-				   &args->nfs_server.export_path,
-				   max_pathlen);
-
-#if !IS_ENABLED(CONFIG_NFS_V4)
-out_v4_not_compiled:
-	dfprintk(MOUNT, "NFS: NFSv4 is not compiled into kernel\n");
-	return -EPROTONOSUPPORT;
-#else
-out_invalid_transport_udp:
-	dfprintk(MOUNT, "NFSv4: Unsupported transport protocol udp\n");
-	return -EINVAL;
-#endif /* !CONFIG_NFS_V4 */
-
-out_no_address:
-	dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
-	return -EINVAL;
-}
-
 #define NFS_REMOUNT_CMP_FLAGMASK ~(NFS_MOUNT_INTR \
 		| NFS_MOUNT_SECURE \
 		| NFS_MOUNT_TCP \
@@ -2724,113 +1440,6 @@ nfs_xdev_mount(struct file_system_type *fs_type, int flags,
 
 #if IS_ENABLED(CONFIG_NFS_V4)
 
-static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)
-{
-	args->flags &= ~(NFS_MOUNT_NONLM|NFS_MOUNT_NOACL|NFS_MOUNT_VER3|
-			 NFS_MOUNT_LOCAL_FLOCK|NFS_MOUNT_LOCAL_FCNTL);
-}
-
-/*
- * Validate NFSv4 mount options
- */
-static int nfs4_validate_mount_data(void *options,
-				    struct nfs_parsed_mount_data *args,
-				    const char *dev_name)
-{
-	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
-	struct nfs4_mount_data *data = (struct nfs4_mount_data *)options;
-	char *c;
-
-	if (data == NULL)
-		goto out_no_data;
-
-	args->version = 4;
-
-	switch (data->version) {
-	case 1:
-		if (data->host_addrlen > sizeof(args->nfs_server.address))
-			goto out_no_address;
-		if (data->host_addrlen == 0)
-			goto out_no_address;
-		args->nfs_server.addrlen = data->host_addrlen;
-		if (copy_from_user(sap, data->host_addr, data->host_addrlen))
-			return -EFAULT;
-		if (!nfs_verify_server_address(sap))
-			goto out_no_address;
-		args->nfs_server.port = ntohs(((struct sockaddr_in *)sap)->sin_port);
-
-		if (data->auth_flavourlen) {
-			rpc_authflavor_t pseudoflavor;
-			if (data->auth_flavourlen > 1)
-				goto out_inval_auth;
-			if (copy_from_user(&pseudoflavor,
-					   data->auth_flavours,
-					   sizeof(pseudoflavor)))
-				return -EFAULT;
-			args->selected_flavor = pseudoflavor;
-		} else
-			args->selected_flavor = RPC_AUTH_UNIX;
-
-		c = strndup_user(data->hostname.data, NFS4_MAXNAMLEN);
-		if (IS_ERR(c))
-			return PTR_ERR(c);
-		args->nfs_server.hostname = c;
-
-		c = strndup_user(data->mnt_path.data, NFS4_MAXPATHLEN);
-		if (IS_ERR(c))
-			return PTR_ERR(c);
-		args->nfs_server.export_path = c;
-		dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", c);
-
-		c = strndup_user(data->client_addr.data, 16);
-		if (IS_ERR(c))
-			return PTR_ERR(c);
-		args->client_address = c;
-
-		/*
-		 * Translate to nfs_parsed_mount_data, which nfs4_fill_super
-		 * can deal with.
-		 */
-
-		args->flags	= data->flags & NFS4_MOUNT_FLAGMASK;
-		args->rsize	= data->rsize;
-		args->wsize	= data->wsize;
-		args->timeo	= data->timeo;
-		args->retrans	= data->retrans;
-		args->acregmin	= data->acregmin;
-		args->acregmax	= data->acregmax;
-		args->acdirmin	= data->acdirmin;
-		args->acdirmax	= data->acdirmax;
-		args->nfs_server.protocol = data->proto;
-		nfs_validate_transport_protocol(args);
-		if (args->nfs_server.protocol == XPRT_TRANSPORT_UDP)
-			goto out_invalid_transport_udp;
-
-		break;
-	default:
-		return NFS_TEXT_DATA;
-	}
-
-	return 0;
-
-out_no_data:
-	dfprintk(MOUNT, "NFS4: mount program didn't pass any mount data\n");
-	return -EINVAL;
-
-out_inval_auth:
-	dfprintk(MOUNT, "NFS4: Invalid number of RPC auth flavours %d\n",
-		 data->auth_flavourlen);
-	return -EINVAL;
-
-out_no_address:
-	dfprintk(MOUNT, "NFS4: mount program didn't pass remote address\n");
-	return -EINVAL;
-
-out_invalid_transport_udp:
-	dfprintk(MOUNT, "NFSv4: Unsupported transport protocol udp\n");
-	return -EINVAL;
-}
-
 /*
  * NFS v4 module parameters need to stay in the
  * NFS client for backwards compatibility

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 17/23] NFS: Constify mount argument match tables [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (15 preceding siblings ...)
  2017-05-22 15:53 ` [PATCH 16/23] NFS: Move sb-configuration bits into their own file " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-22 15:54 ` [PATCH 18/23] NFS: Rename struct nfs_parsed_mount_data to struct nfs_sb_config " David Howells
                   ` (9 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

The mount argument match tables should never be altered so constify them.

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

 fs/nfs/mount.c |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index 3e2fe57b9b07..a41f034aafb2 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -197,7 +197,7 @@ enum {
 	Opt_lookupcache_err
 };
 
-static match_table_t nfs_lookupcache_tokens = {
+static const match_table_t nfs_lookupcache_tokens = {
 	{ Opt_lookupcache_all, "all" },
 	{ Opt_lookupcache_positive, "pos" },
 	{ Opt_lookupcache_positive, "positive" },
@@ -213,7 +213,7 @@ enum {
 	Opt_local_lock_err
 };
 
-static match_table_t nfs_local_lock_tokens = {
+static const match_table_t nfs_local_lock_tokens = {
 	{ Opt_local_lock_all, "all" },
 	{ Opt_local_lock_flock, "flock" },
 	{ Opt_local_lock_posix, "posix" },
@@ -229,7 +229,7 @@ enum {
 	Opt_vers_err
 };
 
-static match_table_t nfs_vers_tokens = {
+static const match_table_t nfs_vers_tokens = {
 	{ Opt_vers_2, "2" },
 	{ Opt_vers_3, "3" },
 	{ Opt_vers_4, "4" },

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 18/23] NFS: Rename struct nfs_parsed_mount_data to struct nfs_sb_config [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (16 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 17/23] NFS: Constify mount argument match tables " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-22 15:54 ` [PATCH 19/23] NFS: Split nfs_parse_mount_options() " David Howells
                   ` (8 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Rename struct nfs_parsed_mount_data to struct nfs_sb_config and rename
pointers to it to "cfg".  At some point this will acquire an sb_config
struct as the first term.

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

 fs/nfs/client.c     |   64 ++++----
 fs/nfs/internal.h   |   14 +-
 fs/nfs/mount.c      |  433 +++++++++++++++++++++++++--------------------------
 fs/nfs/nfs4client.c |   58 +++----
 fs/nfs/nfs4super.c  |   10 +
 fs/nfs/super.c      |  184 +++++++++++-----------
 6 files changed, 381 insertions(+), 382 deletions(-)

diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index ee5ddbd36088..5701f5122a64 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -635,25 +635,25 @@ EXPORT_SYMBOL_GPL(nfs_init_client);
  * Create a version 2 or 3 client
  */
 static int nfs_init_server(struct nfs_server *server,
-			   const struct nfs_parsed_mount_data *data,
+			   const struct nfs_sb_config *cfg,
 			   struct nfs_subversion *nfs_mod)
 {
 	struct rpc_timeout timeparms;
 	struct nfs_client_initdata cl_init = {
-		.hostname = data->nfs_server.hostname,
-		.addr = (const struct sockaddr *)&data->nfs_server.address,
-		.addrlen = data->nfs_server.addrlen,
+		.hostname = cfg->nfs_server.hostname,
+		.addr = (const struct sockaddr *)&cfg->nfs_server.address,
+		.addrlen = cfg->nfs_server.addrlen,
 		.nfs_mod = nfs_mod,
-		.proto = data->nfs_server.protocol,
-		.net = data->net,
+		.proto = cfg->nfs_server.protocol,
+		.net = cfg->net,
 		.timeparms = &timeparms,
 	};
 	struct nfs_client *clp;
 	int error;
 
-	nfs_init_timeout_values(&timeparms, data->nfs_server.protocol,
-			data->timeo, data->retrans);
-	if (data->flags & NFS_MOUNT_NORESVPORT)
+	nfs_init_timeout_values(&timeparms, cfg->nfs_server.protocol,
+				cfg->timeo, cfg->retrans);
+	if (cfg->flags & NFS_MOUNT_NORESVPORT)
 		set_bit(NFS_CS_NORESVPORT, &cl_init.init_flags);
 
 	/* Allocate or find a client reference we can use */
@@ -664,46 +664,46 @@ static int nfs_init_server(struct nfs_server *server,
 	server->nfs_client = clp;
 
 	/* Initialise the client representation from the mount data */
-	server->flags = data->flags;
-	server->options = data->options;
+	server->flags = cfg->flags;
+	server->options = cfg->options;
 	server->caps |= NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
 		NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|NFS_CAP_OWNER_GROUP|
 		NFS_CAP_ATIME|NFS_CAP_CTIME|NFS_CAP_MTIME;
 
-	if (data->rsize)
-		server->rsize = nfs_block_size(data->rsize, NULL);
-	if (data->wsize)
-		server->wsize = nfs_block_size(data->wsize, NULL);
+	if (cfg->rsize)
+		server->rsize = nfs_block_size(cfg->rsize, NULL);
+	if (cfg->wsize)
+		server->wsize = nfs_block_size(cfg->wsize, NULL);
 
-	server->acregmin = data->acregmin * HZ;
-	server->acregmax = data->acregmax * HZ;
-	server->acdirmin = data->acdirmin * HZ;
-	server->acdirmax = data->acdirmax * HZ;
+	server->acregmin = cfg->acregmin * HZ;
+	server->acregmax = cfg->acregmax * HZ;
+	server->acdirmin = cfg->acdirmin * HZ;
+	server->acdirmax = cfg->acdirmax * HZ;
 
 	/* Start lockd here, before we might error out */
 	error = nfs_start_lockd(server);
 	if (error < 0)
 		goto error;
 
-	server->port = data->nfs_server.port;
-	server->auth_info = data->auth_info;
+	server->port = cfg->nfs_server.port;
+	server->auth_info = cfg->auth_info;
 
 	error = nfs_init_server_rpcclient(server, &timeparms,
-					  data->selected_flavor);
+					  cfg->selected_flavor);
 	if (error < 0)
 		goto error;
 
 	/* Preserve the values of mount_server-related mount options */
-	if (data->mount_server.addrlen) {
-		memcpy(&server->mountd_address, &data->mount_server.address,
-			data->mount_server.addrlen);
-		server->mountd_addrlen = data->mount_server.addrlen;
+	if (cfg->mount_server.addrlen) {
+		memcpy(&server->mountd_address, &cfg->mount_server.address,
+			cfg->mount_server.addrlen);
+		server->mountd_addrlen = cfg->mount_server.addrlen;
 	}
-	server->mountd_version = data->mount_server.version;
-	server->mountd_port = data->mount_server.port;
-	server->mountd_protocol = data->mount_server.protocol;
+	server->mountd_version = cfg->mount_server.version;
+	server->mountd_port = cfg->mount_server.port;
+	server->mountd_protocol = cfg->mount_server.protocol;
 
-	server->namelen  = data->namlen;
+	server->namelen  = cfg->namlen;
 	return 0;
 
 error:
@@ -938,7 +938,7 @@ struct nfs_server *nfs_create_server(struct nfs_mount_info *mount_info,
 		goto error;
 
 	/* Get a client representation */
-	error = nfs_init_server(server, mount_info->parsed, nfs_mod);
+	error = nfs_init_server(server, mount_info->cfg, nfs_mod);
 	if (error < 0)
 		goto error;
 
@@ -949,7 +949,7 @@ struct nfs_server *nfs_create_server(struct nfs_mount_info *mount_info,
 	if (server->nfs_client->rpc_ops->version == 3) {
 		if (server->namelen == 0 || server->namelen > NFS3_MAXNAMLEN)
 			server->namelen = NFS3_MAXNAMLEN;
-		if (!(mount_info->parsed->flags & NFS_MOUNT_NORDIRPLUS))
+		if (!(mount_info->cfg->flags & NFS_MOUNT_NORDIRPLUS))
 			server->caps |= NFS_CAP_READDIRPLUS;
 	} else {
 		if (server->namelen == 0 || server->namelen > NFS2_MAXNAMLEN)
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 260494be399e..af6ca28da48d 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -85,7 +85,7 @@ struct nfs_client_initdata {
 /*
  * In-kernel mount arguments
  */
-struct nfs_parsed_mount_data {
+struct nfs_sb_config {
 	int			flags;
 	unsigned int		rsize, wsize;
 	unsigned int		timeo, retrans;
@@ -142,7 +142,7 @@ struct nfs_mount_request {
 struct nfs_mount_info {
 	void (*fill_super)(struct super_block *, struct nfs_mount_info *);
 	int (*set_security)(struct super_block *, struct dentry *, struct nfs_mount_info *);
-	struct nfs_parsed_mount_data *parsed;
+	struct nfs_sb_config *cfg;
 	struct nfs_clone_mount *cloned;
 	struct nfs_fh *mntfh;
 };
@@ -234,16 +234,16 @@ struct nfs_pageio_descriptor;
 /* mount.c */
 #define NFS_TEXT_DATA		1
 
-extern struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void);
-extern void nfs_free_parsed_mount_data(struct nfs_parsed_mount_data *data);
-extern int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt);
+extern struct nfs_sb_config *nfs_alloc_parsed_mount_data(void);
+extern void nfs_free_parsed_mount_data(struct nfs_sb_config *cfg);
+extern int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg);
 extern int nfs_validate_mount_data(struct file_system_type *fs_type,
 				   void *options,
-				   struct nfs_parsed_mount_data *args,
+				   struct nfs_sb_config *cfg,
 				   struct nfs_fh *mntfh,
 				   const char *dev_name);
 extern int nfs_validate_text_mount_data(void *options,
-					struct nfs_parsed_mount_data *args,
+					struct nfs_sb_config *cfg,
 					const char *dev_name);
 
 /* pagelist.c */
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index a41f034aafb2..8f22163a4cc7 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -240,40 +240,40 @@ static const match_table_t nfs_vers_tokens = {
 	{ Opt_vers_err, NULL }
 };
 
-struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void)
+struct nfs_sb_config *nfs_alloc_parsed_mount_data(void)
 {
-	struct nfs_parsed_mount_data *data;
-
-	data = kzalloc(sizeof(*data), GFP_KERNEL);
-	if (data) {
-		data->timeo		= NFS_UNSPEC_TIMEO;
-		data->retrans		= NFS_UNSPEC_RETRANS;
-		data->acregmin		= NFS_DEF_ACREGMIN;
-		data->acregmax		= NFS_DEF_ACREGMAX;
-		data->acdirmin		= NFS_DEF_ACDIRMIN;
-		data->acdirmax		= NFS_DEF_ACDIRMAX;
-		data->mount_server.port	= NFS_UNSPEC_PORT;
-		data->nfs_server.port	= NFS_UNSPEC_PORT;
-		data->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-		data->selected_flavor	= RPC_AUTH_MAXFLAVOR;
-		data->minorversion	= 0;
-		data->need_mount	= true;
-		data->net		= current->nsproxy->net_ns;
-		security_init_mnt_opts(&data->lsm_opts);
+	struct nfs_sb_config *cfg;
+
+	cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+	if (cfg) {
+		cfg->timeo		= NFS_UNSPEC_TIMEO;
+		cfg->retrans		= NFS_UNSPEC_RETRANS;
+		cfg->acregmin		= NFS_DEF_ACREGMIN;
+		cfg->acregmax		= NFS_DEF_ACREGMAX;
+		cfg->acdirmin		= NFS_DEF_ACDIRMIN;
+		cfg->acdirmax		= NFS_DEF_ACDIRMAX;
+		cfg->mount_server.port	= NFS_UNSPEC_PORT;
+		cfg->nfs_server.port	= NFS_UNSPEC_PORT;
+		cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+		cfg->selected_flavor	= RPC_AUTH_MAXFLAVOR;
+		cfg->minorversion	= 0;
+		cfg->need_mount	= true;
+		cfg->net		= current->nsproxy->net_ns;
+		security_init_mnt_opts(&cfg->lsm_opts);
 	}
-	return data;
+	return cfg;
 }
 
-void nfs_free_parsed_mount_data(struct nfs_parsed_mount_data *data)
+void nfs_free_parsed_mount_data(struct nfs_sb_config *cfg)
 {
-	if (data) {
-		kfree(data->client_address);
-		kfree(data->mount_server.hostname);
-		kfree(data->nfs_server.export_path);
-		kfree(data->nfs_server.hostname);
-		kfree(data->fscache_uniq);
-		security_free_mnt_opts(&data->lsm_opts);
-		kfree(data);
+	if (cfg) {
+		kfree(cfg->client_address);
+		kfree(cfg->mount_server.hostname);
+		kfree(cfg->nfs_server.export_path);
+		kfree(cfg->nfs_server.hostname);
+		kfree(cfg->fscache_uniq);
+		security_free_mnt_opts(&cfg->lsm_opts);
+		kfree(cfg);
 	}
 }
 
@@ -304,15 +304,15 @@ static int nfs_verify_server_address(struct sockaddr *addr)
  * Sanity check the NFS transport protocol.
  *
  */
-static void nfs_validate_transport_protocol(struct nfs_parsed_mount_data *mnt)
+static void nfs_validate_transport_protocol(struct nfs_sb_config *cfg)
 {
-	switch (mnt->nfs_server.protocol) {
+	switch (cfg->nfs_server.protocol) {
 	case XPRT_TRANSPORT_UDP:
 	case XPRT_TRANSPORT_TCP:
 	case XPRT_TRANSPORT_RDMA:
 		break;
 	default:
-		mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+		cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
 	}
 }
 
@@ -320,20 +320,20 @@ static void nfs_validate_transport_protocol(struct nfs_parsed_mount_data *mnt)
  * For text based NFSv2/v3 mounts, the mount protocol transport default
  * settings should depend upon the specified NFS transport.
  */
-static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt)
+static void nfs_set_mount_transport_protocol(struct nfs_sb_config *cfg)
 {
-	nfs_validate_transport_protocol(mnt);
+	nfs_validate_transport_protocol(cfg);
 
-	if (mnt->mount_server.protocol == XPRT_TRANSPORT_UDP ||
-	    mnt->mount_server.protocol == XPRT_TRANSPORT_TCP)
+	if (cfg->mount_server.protocol == XPRT_TRANSPORT_UDP ||
+	    cfg->mount_server.protocol == XPRT_TRANSPORT_TCP)
 			return;
-	switch (mnt->nfs_server.protocol) {
+	switch (cfg->nfs_server.protocol) {
 	case XPRT_TRANSPORT_UDP:
-		mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
+		cfg->mount_server.protocol = XPRT_TRANSPORT_UDP;
 		break;
 	case XPRT_TRANSPORT_TCP:
 	case XPRT_TRANSPORT_RDMA:
-		mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
+		cfg->mount_server.protocol = XPRT_TRANSPORT_TCP;
 	}
 }
 
@@ -365,8 +365,7 @@ static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
 /*
  * Parse the value of the 'sec=' option.
  */
-static int nfs_parse_security_flavors(char *value,
-				      struct nfs_parsed_mount_data *mnt)
+static int nfs_parse_security_flavors(char *value, struct nfs_sb_config *cfg)
 {
 	substring_t args[MAX_OPT_ARGS];
 	rpc_authflavor_t pseudoflavor;
@@ -415,7 +414,7 @@ static int nfs_parse_security_flavors(char *value,
 			return 0;
 		}
 
-		if (!nfs_auth_info_add(&mnt->auth_info, pseudoflavor))
+		if (!nfs_auth_info_add(&cfg->auth_info, pseudoflavor))
 			return 0;
 	}
 
@@ -423,36 +422,36 @@ static int nfs_parse_security_flavors(char *value,
 }
 
 static int nfs_parse_version_string(char *string,
-		struct nfs_parsed_mount_data *mnt,
+		struct nfs_sb_config *cfg,
 		substring_t *args)
 {
-	mnt->flags &= ~NFS_MOUNT_VER3;
+	cfg->flags &= ~NFS_MOUNT_VER3;
 	switch (match_token(string, nfs_vers_tokens, args)) {
 	case Opt_vers_2:
-		mnt->version = 2;
+		cfg->version = 2;
 		break;
 	case Opt_vers_3:
-		mnt->flags |= NFS_MOUNT_VER3;
-		mnt->version = 3;
+		cfg->flags |= NFS_MOUNT_VER3;
+		cfg->version = 3;
 		break;
 	case Opt_vers_4:
 		/* Backward compatibility option. In future,
 		 * the mount program should always supply
 		 * a NFSv4 minor version number.
 		 */
-		mnt->version = 4;
+		cfg->version = 4;
 		break;
 	case Opt_vers_4_0:
-		mnt->version = 4;
-		mnt->minorversion = 0;
+		cfg->version = 4;
+		cfg->minorversion = 0;
 		break;
 	case Opt_vers_4_1:
-		mnt->version = 4;
-		mnt->minorversion = 1;
+		cfg->version = 4;
+		cfg->minorversion = 1;
 		break;
 	case Opt_vers_4_2:
-		mnt->version = 4;
-		mnt->minorversion = 2;
+		cfg->version = 4;
+		cfg->minorversion = 2;
 		break;
 	default:
 		return 0;
@@ -500,7 +499,7 @@ static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
  * skipped as they are encountered.  If there were no errors, return 1;
  * otherwise return 0 (zero).
  */
-int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
+int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 {
 	char *p, *string, *secdata;
 	int rc, sloppy = 0, invalid_option = 0;
@@ -521,7 +520,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 	if (rc)
 		goto out_security_failure;
 
-	rc = security_sb_parse_opts_str(secdata, &mnt->lsm_opts);
+	rc = security_sb_parse_opts_str(secdata, &cfg->lsm_opts);
 	if (rc)
 		goto out_security_failure;
 
@@ -544,91 +543,91 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 		 * boolean options:  foo/nofoo
 		 */
 		case Opt_soft:
-			mnt->flags |= NFS_MOUNT_SOFT;
+			cfg->flags |= NFS_MOUNT_SOFT;
 			break;
 		case Opt_hard:
-			mnt->flags &= ~NFS_MOUNT_SOFT;
+			cfg->flags &= ~NFS_MOUNT_SOFT;
 			break;
 		case Opt_posix:
-			mnt->flags |= NFS_MOUNT_POSIX;
+			cfg->flags |= NFS_MOUNT_POSIX;
 			break;
 		case Opt_noposix:
-			mnt->flags &= ~NFS_MOUNT_POSIX;
+			cfg->flags &= ~NFS_MOUNT_POSIX;
 			break;
 		case Opt_cto:
-			mnt->flags &= ~NFS_MOUNT_NOCTO;
+			cfg->flags &= ~NFS_MOUNT_NOCTO;
 			break;
 		case Opt_nocto:
-			mnt->flags |= NFS_MOUNT_NOCTO;
+			cfg->flags |= NFS_MOUNT_NOCTO;
 			break;
 		case Opt_ac:
-			mnt->flags &= ~NFS_MOUNT_NOAC;
+			cfg->flags &= ~NFS_MOUNT_NOAC;
 			break;
 		case Opt_noac:
-			mnt->flags |= NFS_MOUNT_NOAC;
+			cfg->flags |= NFS_MOUNT_NOAC;
 			break;
 		case Opt_lock:
-			mnt->flags &= ~NFS_MOUNT_NONLM;
-			mnt->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
+			cfg->flags &= ~NFS_MOUNT_NONLM;
+			cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
 					NFS_MOUNT_LOCAL_FCNTL);
 			break;
 		case Opt_nolock:
-			mnt->flags |= NFS_MOUNT_NONLM;
-			mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |
+			cfg->flags |= NFS_MOUNT_NONLM;
+			cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
 				       NFS_MOUNT_LOCAL_FCNTL);
 			break;
 		case Opt_udp:
-			mnt->flags &= ~NFS_MOUNT_TCP;
-			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+			cfg->flags &= ~NFS_MOUNT_TCP;
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
 			break;
 		case Opt_tcp:
-			mnt->flags |= NFS_MOUNT_TCP;
-			mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+			cfg->flags |= NFS_MOUNT_TCP;
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
 			break;
 		case Opt_rdma:
-			mnt->flags |= NFS_MOUNT_TCP; /* for side protocols */
-			mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
+			cfg->flags |= NFS_MOUNT_TCP; /* for side protocols */
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
 			xprt_load_transport(p);
 			break;
 		case Opt_acl:
-			mnt->flags &= ~NFS_MOUNT_NOACL;
+			cfg->flags &= ~NFS_MOUNT_NOACL;
 			break;
 		case Opt_noacl:
-			mnt->flags |= NFS_MOUNT_NOACL;
+			cfg->flags |= NFS_MOUNT_NOACL;
 			break;
 		case Opt_rdirplus:
-			mnt->flags &= ~NFS_MOUNT_NORDIRPLUS;
+			cfg->flags &= ~NFS_MOUNT_NORDIRPLUS;
 			break;
 		case Opt_nordirplus:
-			mnt->flags |= NFS_MOUNT_NORDIRPLUS;
+			cfg->flags |= NFS_MOUNT_NORDIRPLUS;
 			break;
 		case Opt_sharecache:
-			mnt->flags &= ~NFS_MOUNT_UNSHARED;
+			cfg->flags &= ~NFS_MOUNT_UNSHARED;
 			break;
 		case Opt_nosharecache:
-			mnt->flags |= NFS_MOUNT_UNSHARED;
+			cfg->flags |= NFS_MOUNT_UNSHARED;
 			break;
 		case Opt_resvport:
-			mnt->flags &= ~NFS_MOUNT_NORESVPORT;
+			cfg->flags &= ~NFS_MOUNT_NORESVPORT;
 			break;
 		case Opt_noresvport:
-			mnt->flags |= NFS_MOUNT_NORESVPORT;
+			cfg->flags |= NFS_MOUNT_NORESVPORT;
 			break;
 		case Opt_fscache:
-			mnt->options |= NFS_OPTION_FSCACHE;
-			kfree(mnt->fscache_uniq);
-			mnt->fscache_uniq = NULL;
+			cfg->options |= NFS_OPTION_FSCACHE;
+			kfree(cfg->fscache_uniq);
+			cfg->fscache_uniq = NULL;
 			break;
 		case Opt_nofscache:
-			mnt->options &= ~NFS_OPTION_FSCACHE;
-			kfree(mnt->fscache_uniq);
-			mnt->fscache_uniq = NULL;
+			cfg->options &= ~NFS_OPTION_FSCACHE;
+			kfree(cfg->fscache_uniq);
+			cfg->fscache_uniq = NULL;
 			break;
 		case Opt_migration:
-			mnt->options |= NFS_OPTION_MIGRATION;
+			cfg->options |= NFS_OPTION_MIGRATION;
 			break;
 		case Opt_nomigration:
-			mnt->options &= NFS_OPTION_MIGRATION;
+			cfg->options &= NFS_OPTION_MIGRATION;
 			break;
 
 		/*
@@ -638,83 +637,83 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			if (nfs_get_option_ul(args, &option) ||
 			    option > USHRT_MAX)
 				goto out_invalid_value;
-			mnt->nfs_server.port = option;
+			cfg->nfs_server.port = option;
 			break;
 		case Opt_rsize:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->rsize = option;
+			cfg->rsize = option;
 			break;
 		case Opt_wsize:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->wsize = option;
+			cfg->wsize = option;
 			break;
 		case Opt_bsize:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->bsize = option;
+			cfg->bsize = option;
 			break;
 		case Opt_timeo:
 			if (nfs_get_option_ul_bound(args, &option, 1, INT_MAX))
 				goto out_invalid_value;
-			mnt->timeo = option;
+			cfg->timeo = option;
 			break;
 		case Opt_retrans:
 			if (nfs_get_option_ul_bound(args, &option, 0, INT_MAX))
 				goto out_invalid_value;
-			mnt->retrans = option;
+			cfg->retrans = option;
 			break;
 		case Opt_acregmin:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->acregmin = option;
+			cfg->acregmin = option;
 			break;
 		case Opt_acregmax:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->acregmax = option;
+			cfg->acregmax = option;
 			break;
 		case Opt_acdirmin:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->acdirmin = option;
+			cfg->acdirmin = option;
 			break;
 		case Opt_acdirmax:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->acdirmax = option;
+			cfg->acdirmax = option;
 			break;
 		case Opt_actimeo:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->acregmin = mnt->acregmax =
-			mnt->acdirmin = mnt->acdirmax = option;
+			cfg->acregmin = cfg->acregmax =
+			cfg->acdirmin = cfg->acdirmax = option;
 			break;
 		case Opt_namelen:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
-			mnt->namlen = option;
+			cfg->namlen = option;
 			break;
 		case Opt_mountport:
 			if (nfs_get_option_ul(args, &option) ||
 			    option > USHRT_MAX)
 				goto out_invalid_value;
-			mnt->mount_server.port = option;
+			cfg->mount_server.port = option;
 			break;
 		case Opt_mountvers:
 			if (nfs_get_option_ul(args, &option) ||
 			    option < NFS_MNT_VERSION ||
 			    option > NFS_MNT3_VERSION)
 				goto out_invalid_value;
-			mnt->mount_server.version = option;
+			cfg->mount_server.version = option;
 			break;
 		case Opt_minorversion:
 			if (nfs_get_option_ul(args, &option))
 				goto out_invalid_value;
 			if (option > NFS4_MAX_MINOR_VERSION)
 				goto out_invalid_value;
-			mnt->minorversion = option;
+			cfg->minorversion = option;
 			break;
 
 		/*
@@ -724,7 +723,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			string = match_strdup(args);
 			if (string == NULL)
 				goto out_nomem;
-			rc = nfs_parse_version_string(string, mnt, args);
+			rc = nfs_parse_version_string(string, cfg, args);
 			kfree(string);
 			if (!rc)
 				goto out_invalid_value;
@@ -733,7 +732,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			string = match_strdup(args);
 			if (string == NULL)
 				goto out_nomem;
-			rc = nfs_parse_security_flavors(string, mnt);
+			rc = nfs_parse_security_flavors(string, cfg);
 			kfree(string);
 			if (!rc) {
 				dfprintk(MOUNT, "NFS:   unrecognized "
@@ -753,21 +752,21 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			case Opt_xprt_udp6:
 				protofamily = AF_INET6;
 			case Opt_xprt_udp:
-				mnt->flags &= ~NFS_MOUNT_TCP;
-				mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+				cfg->flags &= ~NFS_MOUNT_TCP;
+				cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
 				break;
 			case Opt_xprt_tcp6:
 				protofamily = AF_INET6;
 			case Opt_xprt_tcp:
-				mnt->flags |= NFS_MOUNT_TCP;
-				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+				cfg->flags |= NFS_MOUNT_TCP;
+				cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
 				break;
 			case Opt_xprt_rdma6:
 				protofamily = AF_INET6;
 			case Opt_xprt_rdma:
 				/* vector side protocols to TCP */
-				mnt->flags |= NFS_MOUNT_TCP;
-				mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
+				cfg->flags |= NFS_MOUNT_TCP;
+				cfg->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
 				xprt_load_transport(string);
 				break;
 			default:
@@ -791,12 +790,12 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			case Opt_xprt_udp6:
 				mountfamily = AF_INET6;
 			case Opt_xprt_udp:
-				mnt->mount_server.protocol = XPRT_TRANSPORT_UDP;
+				cfg->mount_server.protocol = XPRT_TRANSPORT_UDP;
 				break;
 			case Opt_xprt_tcp6:
 				mountfamily = AF_INET6;
 			case Opt_xprt_tcp:
-				mnt->mount_server.protocol = XPRT_TRANSPORT_TCP;
+				cfg->mount_server.protocol = XPRT_TRANSPORT_TCP;
 				break;
 			case Opt_xprt_rdma: /* not used for side protocols */
 			default:
@@ -809,35 +808,35 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			string = match_strdup(args);
 			if (string == NULL)
 				goto out_nomem;
-			mnt->nfs_server.addrlen =
-				rpc_pton(mnt->net, string, strlen(string),
+			cfg->nfs_server.addrlen =
+				rpc_pton(cfg->net, string, strlen(string),
 					(struct sockaddr *)
-					&mnt->nfs_server.address,
-					sizeof(mnt->nfs_server.address));
+					&cfg->nfs_server.address,
+					sizeof(cfg->nfs_server.address));
 			kfree(string);
-			if (mnt->nfs_server.addrlen == 0)
+			if (cfg->nfs_server.addrlen == 0)
 				goto out_invalid_address;
 			break;
 		case Opt_clientaddr:
-			if (nfs_get_option_str(args, &mnt->client_address))
+			if (nfs_get_option_str(args, &cfg->client_address))
 				goto out_nomem;
 			break;
 		case Opt_mounthost:
 			if (nfs_get_option_str(args,
-					       &mnt->mount_server.hostname))
+					       &cfg->mount_server.hostname))
 				goto out_nomem;
 			break;
 		case Opt_mountaddr:
 			string = match_strdup(args);
 			if (string == NULL)
 				goto out_nomem;
-			mnt->mount_server.addrlen =
-				rpc_pton(mnt->net, string, strlen(string),
+			cfg->mount_server.addrlen =
+				rpc_pton(cfg->net, string, strlen(string),
 					(struct sockaddr *)
-					&mnt->mount_server.address,
-					sizeof(mnt->mount_server.address));
+					&cfg->mount_server.address,
+					sizeof(cfg->mount_server.address));
 			kfree(string);
-			if (mnt->mount_server.addrlen == 0)
+			if (cfg->mount_server.addrlen == 0)
 				goto out_invalid_address;
 			break;
 		case Opt_lookupcache:
@@ -849,14 +848,14 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			kfree(string);
 			switch (token) {
 				case Opt_lookupcache_all:
-					mnt->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
+					cfg->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
 					break;
 				case Opt_lookupcache_positive:
-					mnt->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
-					mnt->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
+					cfg->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
+					cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
 					break;
 				case Opt_lookupcache_none:
-					mnt->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
+					cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
 					break;
 				default:
 					dfprintk(MOUNT, "NFS:   invalid "
@@ -865,9 +864,9 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			};
 			break;
 		case Opt_fscache_uniq:
-			if (nfs_get_option_str(args, &mnt->fscache_uniq))
+			if (nfs_get_option_str(args, &cfg->fscache_uniq))
 				goto out_nomem;
-			mnt->options |= NFS_OPTION_FSCACHE;
+			cfg->options |= NFS_OPTION_FSCACHE;
 			break;
 		case Opt_local_lock:
 			string = match_strdup(args);
@@ -878,17 +877,17 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 			kfree(string);
 			switch (token) {
 			case Opt_local_lock_all:
-				mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |
+				cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
 					       NFS_MOUNT_LOCAL_FCNTL);
 				break;
 			case Opt_local_lock_flock:
-				mnt->flags |= NFS_MOUNT_LOCAL_FLOCK;
+				cfg->flags |= NFS_MOUNT_LOCAL_FLOCK;
 				break;
 			case Opt_local_lock_posix:
-				mnt->flags |= NFS_MOUNT_LOCAL_FCNTL;
+				cfg->flags |= NFS_MOUNT_LOCAL_FCNTL;
 				break;
 			case Opt_local_lock_none:
-				mnt->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
+				cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
 						NFS_MOUNT_LOCAL_FCNTL);
 				break;
 			default:
@@ -921,11 +920,11 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 	if (!sloppy && invalid_option)
 		return 0;
 
-	if (mnt->minorversion && mnt->version != 4)
+	if (cfg->minorversion && cfg->version != 4)
 		goto out_minorversion_mismatch;
 
-	if (mnt->options & NFS_OPTION_MIGRATION &&
-	    (mnt->version != 4 || mnt->minorversion != 0))
+	if (cfg->options & NFS_OPTION_MIGRATION &&
+	    (cfg->version != 4 || cfg->minorversion != 0))
 		goto out_migration_misuse;
 
 	/*
@@ -933,15 +932,15 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 	 * families in the addr=/mountaddr= options.
 	 */
 	if (protofamily != AF_UNSPEC &&
-	    protofamily != mnt->nfs_server.address.ss_family)
+	    protofamily != cfg->nfs_server.address.ss_family)
 		goto out_proto_mismatch;
 
 	if (mountfamily != AF_UNSPEC) {
-		if (mnt->mount_server.addrlen) {
-			if (mountfamily != mnt->mount_server.address.ss_family)
+		if (cfg->mount_server.addrlen) {
+			if (mountfamily != cfg->mount_server.address.ss_family)
 				goto out_mountproto_mismatch;
 		} else {
-			if (mountfamily != mnt->nfs_server.address.ss_family)
+			if (mountfamily != cfg->nfs_server.address.ss_family)
 				goto out_mountproto_mismatch;
 		}
 	}
@@ -963,7 +962,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_parsed_mount_data *mnt)
 	return 0;
 out_minorversion_mismatch:
 	printk(KERN_INFO "NFS: mount option vers=%u does not support "
-			 "minorversion=%u\n", mnt->version, mnt->minorversion);
+			 "minorversion=%u\n", cfg->version, cfg->minorversion);
 	return 0;
 out_migration_misuse:
 	printk(KERN_INFO
@@ -1067,18 +1066,18 @@ static int nfs_parse_devname(const char *dev_name,
  *   mountproto=tcp after mountproto=udp, and so on
  */
 static int nfs23_validate_mount_data(void *options,
-				     struct nfs_parsed_mount_data *args,
+				     struct nfs_sb_config *cfg,
 				     struct nfs_fh *mntfh,
 				     const char *dev_name)
 {
 	struct nfs_mount_data *data = (struct nfs_mount_data *)options;
-	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
 	int extra_flags = NFS_MOUNT_LEGACY_INTERFACE;
 
 	if (data == NULL)
 		goto out_no_data;
 
-	args->version = NFS_DEFAULT_VERSION;
+	cfg->version = NFS_DEFAULT_VERSION;
 	switch (data->version) {
 	case 1:
 		data->namlen = 0;
@@ -1101,10 +1100,10 @@ static int nfs23_validate_mount_data(void *options,
 			if (data->root.size > NFS3_FHSIZE || data->root.size == 0)
 				goto out_invalid_fh;
 			mntfh->size = data->root.size;
-			args->version = 3;
+			cfg->version = 3;
 		} else {
 			mntfh->size = NFS2_FHSIZE;
-			args->version = 2;
+			cfg->version = 2;
 		}
 
 
@@ -1114,46 +1113,46 @@ static int nfs23_validate_mount_data(void *options,
 			       sizeof(mntfh->data) - mntfh->size);
 
 		/*
-		 * Translate to nfs_parsed_mount_data, which nfs_fill_super
+		 * Translate to nfs_sb_config, which nfs_fill_super
 		 * can deal with.
 		 */
-		args->flags		= data->flags & NFS_MOUNT_FLAGMASK;
-		args->flags		|= extra_flags;
-		args->rsize		= data->rsize;
-		args->wsize		= data->wsize;
-		args->timeo		= data->timeo;
-		args->retrans		= data->retrans;
-		args->acregmin		= data->acregmin;
-		args->acregmax		= data->acregmax;
-		args->acdirmin		= data->acdirmin;
-		args->acdirmax		= data->acdirmax;
-		args->need_mount	= false;
+		cfg->flags		= data->flags & NFS_MOUNT_FLAGMASK;
+		cfg->flags		|= extra_flags;
+		cfg->rsize		= data->rsize;
+		cfg->wsize		= data->wsize;
+		cfg->timeo		= data->timeo;
+		cfg->retrans		= data->retrans;
+		cfg->acregmin		= data->acregmin;
+		cfg->acregmax		= data->acregmax;
+		cfg->acdirmin		= data->acdirmin;
+		cfg->acdirmax		= data->acdirmax;
+		cfg->need_mount	= false;
 
 		memcpy(sap, &data->addr, sizeof(data->addr));
-		args->nfs_server.addrlen = sizeof(data->addr);
-		args->nfs_server.port = ntohs(data->addr.sin_port);
+		cfg->nfs_server.addrlen = sizeof(data->addr);
+		cfg->nfs_server.port = ntohs(data->addr.sin_port);
 		if (!nfs_verify_server_address(sap))
 			goto out_no_address;
 
 		if (!(data->flags & NFS_MOUNT_TCP))
-			args->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
 		/* N.B. caller will free nfs_server.hostname in all cases */
-		args->nfs_server.hostname = kstrdup(data->hostname, GFP_KERNEL);
-		args->namlen		= data->namlen;
-		args->bsize		= data->bsize;
+		cfg->nfs_server.hostname = kstrdup(data->hostname, GFP_KERNEL);
+		cfg->namlen		= data->namlen;
+		cfg->bsize		= data->bsize;
 
 		if (data->flags & NFS_MOUNT_SECFLAVOUR)
-			args->selected_flavor = data->pseudoflavor;
+			cfg->selected_flavor = data->pseudoflavor;
 		else
-			args->selected_flavor = RPC_AUTH_UNIX;
-		if (!args->nfs_server.hostname)
+			cfg->selected_flavor = RPC_AUTH_UNIX;
+		if (!cfg->nfs_server.hostname)
 			goto out_nomem;
 
 		if (!(data->flags & NFS_MOUNT_NONLM))
-			args->flags &= ~(NFS_MOUNT_LOCAL_FLOCK|
+			cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK|
 					 NFS_MOUNT_LOCAL_FCNTL);
 		else
-			args->flags |= (NFS_MOUNT_LOCAL_FLOCK|
+			cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK|
 					NFS_MOUNT_LOCAL_FCNTL);
 		/*
 		 * The legacy version 6 binary mount data from userspace has a
@@ -1172,7 +1171,7 @@ static int nfs23_validate_mount_data(void *options,
 			strcpy(opts_str, "context=");
 			data->context[NFS_MAX_CONTEXT_LEN] = '\0';
 			strcat(opts_str, &data->context[0]);
-			rc = security_sb_parse_opts_str(opts_str, &args->lsm_opts);
+			rc = security_sb_parse_opts_str(opts_str, &cfg->lsm_opts);
 			kfree(opts_str);
 			if (rc)
 				return rc;
@@ -1216,9 +1215,9 @@ static int nfs23_validate_mount_data(void *options,
 
 #if IS_ENABLED(CONFIG_NFS_V4)
 
-static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)
+static void nfs4_validate_mount_flags(struct nfs_sb_config *cfg)
 {
-	args->flags &= ~(NFS_MOUNT_NONLM|NFS_MOUNT_NOACL|NFS_MOUNT_VER3|
+	cfg->flags &= ~(NFS_MOUNT_NONLM|NFS_MOUNT_NOACL|NFS_MOUNT_VER3|
 			 NFS_MOUNT_LOCAL_FLOCK|NFS_MOUNT_LOCAL_FCNTL);
 }
 
@@ -1226,30 +1225,30 @@ static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)
  * Validate NFSv4 mount options
  */
 static int nfs4_validate_mount_data(void *options,
-				    struct nfs_parsed_mount_data *args,
+				    struct nfs_sb_config *cfg,
 				    const char *dev_name)
 {
-	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
 	struct nfs4_mount_data *data = (struct nfs4_mount_data *)options;
 	char *c;
 
 	if (data == NULL)
 		goto out_no_data;
 
-	args->version = 4;
+	cfg->version = 4;
 
 	switch (data->version) {
 	case 1:
-		if (data->host_addrlen > sizeof(args->nfs_server.address))
+		if (data->host_addrlen > sizeof(cfg->nfs_server.address))
 			goto out_no_address;
 		if (data->host_addrlen == 0)
 			goto out_no_address;
-		args->nfs_server.addrlen = data->host_addrlen;
+		cfg->nfs_server.addrlen = data->host_addrlen;
 		if (copy_from_user(sap, data->host_addr, data->host_addrlen))
 			return -EFAULT;
 		if (!nfs_verify_server_address(sap))
 			goto out_no_address;
-		args->nfs_server.port = ntohs(((struct sockaddr_in *)sap)->sin_port);
+		cfg->nfs_server.port = ntohs(((struct sockaddr_in *)sap)->sin_port);
 
 		if (data->auth_flavourlen) {
 			rpc_authflavor_t pseudoflavor;
@@ -1259,43 +1258,43 @@ static int nfs4_validate_mount_data(void *options,
 					   data->auth_flavours,
 					   sizeof(pseudoflavor)))
 				return -EFAULT;
-			args->selected_flavor = pseudoflavor;
+			cfg->selected_flavor = pseudoflavor;
 		} else
-			args->selected_flavor = RPC_AUTH_UNIX;
+			cfg->selected_flavor = RPC_AUTH_UNIX;
 
 		c = strndup_user(data->hostname.data, NFS4_MAXNAMLEN);
 		if (IS_ERR(c))
 			return PTR_ERR(c);
-		args->nfs_server.hostname = c;
+		cfg->nfs_server.hostname = c;
 
 		c = strndup_user(data->mnt_path.data, NFS4_MAXPATHLEN);
 		if (IS_ERR(c))
 			return PTR_ERR(c);
-		args->nfs_server.export_path = c;
+		cfg->nfs_server.export_path = c;
 		dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", c);
 
 		c = strndup_user(data->client_addr.data, 16);
 		if (IS_ERR(c))
 			return PTR_ERR(c);
-		args->client_address = c;
+		cfg->client_address = c;
 
 		/*
-		 * Translate to nfs_parsed_mount_data, which nfs4_fill_super
+		 * Translate to nfs_sb_config, which nfs4_fill_super
 		 * can deal with.
 		 */
 
-		args->flags	= data->flags & NFS4_MOUNT_FLAGMASK;
-		args->rsize	= data->rsize;
-		args->wsize	= data->wsize;
-		args->timeo	= data->timeo;
-		args->retrans	= data->retrans;
-		args->acregmin	= data->acregmin;
-		args->acregmax	= data->acregmax;
-		args->acdirmin	= data->acdirmin;
-		args->acdirmax	= data->acdirmax;
-		args->nfs_server.protocol = data->proto;
-		nfs_validate_transport_protocol(args);
-		if (args->nfs_server.protocol == XPRT_TRANSPORT_UDP)
+		cfg->flags	= data->flags & NFS4_MOUNT_FLAGMASK;
+		cfg->rsize	= data->rsize;
+		cfg->wsize	= data->wsize;
+		cfg->timeo	= data->timeo;
+		cfg->retrans	= data->retrans;
+		cfg->acregmin	= data->acregmin;
+		cfg->acregmax	= data->acregmax;
+		cfg->acdirmin	= data->acdirmin;
+		cfg->acdirmax	= data->acdirmax;
+		cfg->nfs_server.protocol = data->proto;
+		nfs_validate_transport_protocol(cfg);
+		if (cfg->nfs_server.protocol == XPRT_TRANSPORT_UDP)
 			goto out_invalid_transport_udp;
 
 		break;
@@ -1325,61 +1324,61 @@ static int nfs4_validate_mount_data(void *options,
 
 int nfs_validate_mount_data(struct file_system_type *fs_type,
 			    void *options,
-			    struct nfs_parsed_mount_data *args,
+			    struct nfs_sb_config *cfg,
 			    struct nfs_fh *mntfh,
 			    const char *dev_name)
 {
 	if (fs_type == &nfs_fs_type)
-		return nfs23_validate_mount_data(options, args, mntfh, dev_name);
-	return nfs4_validate_mount_data(options, args, dev_name);
+		return nfs23_validate_mount_data(options, cfg, mntfh, dev_name);
+	return nfs4_validate_mount_data(options, cfg, dev_name);
 }
 #else
 int nfs_validate_mount_data(struct file_system_type *fs_type,
 			    void *options,
-			    struct nfs_parsed_mount_data *args,
+			    struct nfs_sb_config *cfg,
 			    struct nfs_fh *mntfh,
 			    const char *dev_name)
 {
-	return nfs23_validate_mount_data(options, args, mntfh, dev_name);
+	return nfs23_validate_mount_data(options, cfg, mntfh, dev_name);
 }
 #endif
 
 int nfs_validate_text_mount_data(void *options,
-				 struct nfs_parsed_mount_data *args,
+				 struct nfs_sb_config *cfg,
 				 const char *dev_name)
 {
 	int port = 0;
 	int max_namelen = PAGE_SIZE;
 	int max_pathlen = NFS_MAXPATHLEN;
-	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address;
+	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
 
-	if (nfs_parse_mount_options((char *)options, args) == 0)
+	if (nfs_parse_mount_options((char *)options, cfg) == 0)
 		return -EINVAL;
 
 	if (!nfs_verify_server_address(sap))
 		goto out_no_address;
 
-	if (args->version == 4) {
+	if (cfg->version == 4) {
 #if IS_ENABLED(CONFIG_NFS_V4)
 		port = NFS_PORT;
 		max_namelen = NFS4_MAXNAMLEN;
 		max_pathlen = NFS4_MAXPATHLEN;
-		nfs_validate_transport_protocol(args);
-		if (args->nfs_server.protocol == XPRT_TRANSPORT_UDP)
+		nfs_validate_transport_protocol(cfg);
+		if (cfg->nfs_server.protocol == XPRT_TRANSPORT_UDP)
 			goto out_invalid_transport_udp;
-		nfs4_validate_mount_flags(args);
+		nfs4_validate_mount_flags(cfg);
 #else
 		goto out_v4_not_compiled;
 #endif /* CONFIG_NFS_V4 */
 	} else
-		nfs_set_mount_transport_protocol(args);
+		nfs_set_mount_transport_protocol(cfg);
 
-	nfs_set_port(sap, &args->nfs_server.port, port);
+	nfs_set_port(sap, &cfg->nfs_server.port, port);
 
 	return nfs_parse_devname(dev_name,
-				   &args->nfs_server.hostname,
+				   &cfg->nfs_server.hostname,
 				   max_namelen,
-				   &args->nfs_server.export_path,
+				   &cfg->nfs_server.export_path,
 				   max_pathlen);
 
 #if !IS_ENABLED(CONFIG_NFS_V4)
diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
index 692a7a8bfc7a..ec0c112d8148 100644
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -1010,60 +1010,60 @@ static int nfs4_server_common_setup(struct nfs_server *server,
  * Create a version 4 volume record
  */
 static int nfs4_init_server(struct nfs_server *server,
-		struct nfs_parsed_mount_data *data)
+			    struct nfs_sb_config *cfg)
 {
 	struct rpc_timeout timeparms;
 	int error;
 
-	nfs_init_timeout_values(&timeparms, data->nfs_server.protocol,
-			data->timeo, data->retrans);
+	nfs_init_timeout_values(&timeparms, cfg->nfs_server.protocol,
+				cfg->timeo, cfg->retrans);
 
 	/* Initialise the client representation from the mount data */
-	server->flags = data->flags;
-	server->options = data->options;
-	server->auth_info = data->auth_info;
+	server->flags = cfg->flags;
+	server->options = cfg->options;
+	server->auth_info = cfg->auth_info;
 
 	/* Use the first specified auth flavor. If this flavor isn't
 	 * allowed by the server, use the SECINFO path to try the
 	 * other specified flavors */
-	if (data->auth_info.flavor_len >= 1)
-		data->selected_flavor = data->auth_info.flavors[0];
+	if (cfg->auth_info.flavor_len >= 1)
+		cfg->selected_flavor = cfg->auth_info.flavors[0];
 	else
-		data->selected_flavor = RPC_AUTH_UNIX;
+		cfg->selected_flavor = RPC_AUTH_UNIX;
 
 	/* Get a client record */
 	error = nfs4_set_client(server,
-			data->nfs_server.hostname,
-			(const struct sockaddr *)&data->nfs_server.address,
-			data->nfs_server.addrlen,
-			data->client_address,
-			data->nfs_server.protocol,
+			cfg->nfs_server.hostname,
+			(const struct sockaddr *)&cfg->nfs_server.address,
+			cfg->nfs_server.addrlen,
+			cfg->client_address,
+			cfg->nfs_server.protocol,
 			&timeparms,
-			data->minorversion,
-			data->net);
+			cfg->minorversion,
+			cfg->net);
 	if (error < 0)
 		return error;
 
-	if (data->rsize)
-		server->rsize = nfs_block_size(data->rsize, NULL);
-	if (data->wsize)
-		server->wsize = nfs_block_size(data->wsize, NULL);
+	if (cfg->rsize)
+		server->rsize = nfs_block_size(cfg->rsize, NULL);
+	if (cfg->wsize)
+		server->wsize = nfs_block_size(cfg->wsize, NULL);
 
-	server->acregmin = data->acregmin * HZ;
-	server->acregmax = data->acregmax * HZ;
-	server->acdirmin = data->acdirmin * HZ;
-	server->acdirmax = data->acdirmax * HZ;
-	server->port     = data->nfs_server.port;
+	server->acregmin = cfg->acregmin * HZ;
+	server->acregmax = cfg->acregmax * HZ;
+	server->acdirmin = cfg->acdirmin * HZ;
+	server->acdirmax = cfg->acdirmax * HZ;
+	server->port     = cfg->nfs_server.port;
 
 	return nfs_init_server_rpcclient(server, &timeparms,
-					 data->selected_flavor);
+					 cfg->selected_flavor);
 }
 
 /*
  * Create a version 4 volume record
  * - keyed on server and FSID
  */
-/*struct nfs_server *nfs4_create_server(const struct nfs_parsed_mount_data *data,
+/*struct nfs_server *nfs4_create_server(const struct nfs_sb_config *data,
 				      struct nfs_fh *mntfh)*/
 struct nfs_server *nfs4_create_server(struct nfs_mount_info *mount_info,
 				      struct nfs_subversion *nfs_mod)
@@ -1076,10 +1076,10 @@ struct nfs_server *nfs4_create_server(struct nfs_mount_info *mount_info,
 	if (!server)
 		return ERR_PTR(-ENOMEM);
 
-	auth_probe = mount_info->parsed->auth_info.flavor_len < 1;
+	auth_probe = mount_info->cfg->auth_info.flavor_len < 1;
 
 	/* set up the general RPC client */
-	error = nfs4_init_server(server, mount_info->parsed);
+	error = nfs4_init_server(server, mount_info->cfg);
 	if (error < 0)
 		goto error;
 
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index 6fb7cb6b3f4b..d160ccd89865 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -241,15 +241,15 @@ struct dentry *nfs4_try_mount(int flags, const char *dev_name,
 	char *export_path;
 	struct vfsmount *root_mnt;
 	struct dentry *res;
-	struct nfs_parsed_mount_data *data = mount_info->parsed;
+	struct nfs_sb_config *cfg = mount_info->cfg;
 
 	dfprintk(MOUNT, "--> nfs4_try_mount()\n");
 
-	export_path = data->nfs_server.export_path;
-	data->nfs_server.export_path = "/";
+	export_path = cfg->nfs_server.export_path;
+	cfg->nfs_server.export_path = "/";
 	root_mnt = nfs_do_root_mount(&nfs4_remote_fs_type, flags, mount_info,
-			data->nfs_server.hostname);
-	data->nfs_server.export_path = export_path;
+			cfg->nfs_server.hostname);
+	cfg->nfs_server.export_path = export_path;
 
 	res = nfs_follow_remote_path(root_mnt, export_path);
 
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 1f39638f4d91..26c1fca31e6b 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -710,11 +710,11 @@ bool nfs_auth_info_match(const struct nfs_auth_info *auth_info,
 EXPORT_SYMBOL_GPL(nfs_auth_info_match);
 
 /*
- * Ensure that a specified authtype in args->auth_info is supported by
- * the server. Returns 0 and sets args->selected_flavor if it's ok, and
+ * Ensure that a specified authtype in cfg->auth_info is supported by
+ * the server. Returns 0 and sets cfg->selected_flavor if it's ok, and
  * -EACCES if not.
  */
-static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args,
+static int nfs_verify_authflavors(struct nfs_sb_config *cfg,
 			rpc_authflavor_t *server_authlist, unsigned int count)
 {
 	rpc_authflavor_t flavor = RPC_AUTH_MAXFLAVOR;
@@ -732,7 +732,7 @@ static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args,
 	for (i = 0; i < count; i++) {
 		flavor = server_authlist[i];
 
-		if (nfs_auth_info_match(&args->auth_info, flavor))
+		if (nfs_auth_info_match(&cfg->auth_info, flavor))
 			goto out;
 
 		if (flavor == RPC_AUTH_NULL)
@@ -749,8 +749,8 @@ static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args,
 	return -EACCES;
 
 out:
-	args->selected_flavor = flavor;
-	dfprintk(MOUNT, "NFS: using auth flavor %u\n", args->selected_flavor);
+	cfg->selected_flavor = flavor;
+	dfprintk(MOUNT, "NFS: using auth flavor %u\n", cfg->selected_flavor);
 	return 0;
 }
 
@@ -758,50 +758,50 @@ static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args,
  * Use the remote server's MOUNT service to request the NFS file handle
  * corresponding to the provided path.
  */
-static int nfs_request_mount(struct nfs_parsed_mount_data *args,
+static int nfs_request_mount(struct nfs_sb_config *cfg,
 			     struct nfs_fh *root_fh,
 			     rpc_authflavor_t *server_authlist,
 			     unsigned int *server_authlist_len)
 {
 	struct nfs_mount_request request = {
 		.sap		= (struct sockaddr *)
-						&args->mount_server.address,
-		.dirpath	= args->nfs_server.export_path,
-		.protocol	= args->mount_server.protocol,
+						&cfg->mount_server.address,
+		.dirpath	= cfg->nfs_server.export_path,
+		.protocol	= cfg->mount_server.protocol,
 		.fh		= root_fh,
-		.noresvport	= args->flags & NFS_MOUNT_NORESVPORT,
+		.noresvport	= cfg->flags & NFS_MOUNT_NORESVPORT,
 		.auth_flav_len	= server_authlist_len,
 		.auth_flavs	= server_authlist,
-		.net		= args->net,
+		.net		= cfg->net,
 	};
 	int status;
 
-	if (args->mount_server.version == 0) {
-		switch (args->version) {
+	if (cfg->mount_server.version == 0) {
+		switch (cfg->version) {
 			default:
-				args->mount_server.version = NFS_MNT3_VERSION;
+				cfg->mount_server.version = NFS_MNT3_VERSION;
 				break;
 			case 2:
-				args->mount_server.version = NFS_MNT_VERSION;
+				cfg->mount_server.version = NFS_MNT_VERSION;
 		}
 	}
-	request.version = args->mount_server.version;
+	request.version = cfg->mount_server.version;
 
-	if (args->mount_server.hostname)
-		request.hostname = args->mount_server.hostname;
+	if (cfg->mount_server.hostname)
+		request.hostname = cfg->mount_server.hostname;
 	else
-		request.hostname = args->nfs_server.hostname;
+		request.hostname = cfg->nfs_server.hostname;
 
 	/*
 	 * Construct the mount server's address.
 	 */
-	if (args->mount_server.address.ss_family == AF_UNSPEC) {
-		memcpy(request.sap, &args->nfs_server.address,
-		       args->nfs_server.addrlen);
-		args->mount_server.addrlen = args->nfs_server.addrlen;
+	if (cfg->mount_server.address.ss_family == AF_UNSPEC) {
+		memcpy(request.sap, &cfg->nfs_server.address,
+		       cfg->nfs_server.addrlen);
+		cfg->mount_server.addrlen = cfg->nfs_server.addrlen;
 	}
-	request.salen = args->mount_server.addrlen;
-	nfs_set_port(request.sap, &args->mount_server.port, 0);
+	request.salen = cfg->mount_server.addrlen;
+	nfs_set_port(request.sap, &cfg->mount_server.port, 0);
 
 	/*
 	 * Now ask the mount server to map our export path
@@ -825,11 +825,11 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 	bool tried_auth_unix = false;
 	bool auth_null_in_list = false;
 	struct nfs_server *server = ERR_PTR(-EACCES);
-	struct nfs_parsed_mount_data *args = mount_info->parsed;
+	struct nfs_sb_config *cfg = mount_info->cfg;
 	rpc_authflavor_t authlist[NFS_MAX_SECFLAVORS];
 	unsigned int authlist_len = ARRAY_SIZE(authlist);
 
-	status = nfs_request_mount(args, mount_info->mntfh, authlist,
+	status = nfs_request_mount(cfg, mount_info->mntfh, authlist,
 					&authlist_len);
 	if (status)
 		return ERR_PTR(status);
@@ -838,10 +838,10 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 	 * Was a sec= authflavor specified in the options? First, verify
 	 * whether the server supports it, and then just try to use it if so.
 	 */
-	if (args->auth_info.flavor_len > 0) {
-		status = nfs_verify_authflavors(args, authlist, authlist_len);
+	if (cfg->auth_info.flavor_len > 0) {
+		status = nfs_verify_authflavors(cfg, authlist, authlist_len);
 		dfprintk(MOUNT, "NFS: using auth flavor %u\n",
-			 args->selected_flavor);
+			 cfg->selected_flavor);
 		if (status)
 			return ERR_PTR(status);
 		return nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
@@ -870,7 +870,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 			/* Fallthrough */
 		}
 		dfprintk(MOUNT, "NFS: attempting to use auth flavor %u\n", flavor);
-		args->selected_flavor = flavor;
+		cfg->selected_flavor = flavor;
 		server = nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
 		if (!IS_ERR(server))
 			return server;
@@ -886,7 +886,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 
 	/* Last chance! Try AUTH_UNIX */
 	dfprintk(MOUNT, "NFS: attempting to use auth flavor %u\n", RPC_AUTH_UNIX);
-	args->selected_flavor = RPC_AUTH_UNIX;
+	cfg->selected_flavor = RPC_AUTH_UNIX;
 	return nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
 }
 
@@ -896,7 +896,7 @@ struct dentry *nfs_try_mount(int flags, const char *dev_name,
 {
 	struct nfs_server *server;
 
-	if (mount_info->parsed->need_mount)
+	if (mount_info->cfg->need_mount)
 		server = nfs_try_mount_request(mount_info, nfs_mod);
 	else
 		server = nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
@@ -923,23 +923,23 @@ EXPORT_SYMBOL_GPL(nfs_try_mount);
 
 static int
 nfs_compare_remount_data(struct nfs_server *nfss,
-			 struct nfs_parsed_mount_data *data)
+			 struct nfs_sb_config *cfg)
 {
-	if ((data->flags ^ nfss->flags) & NFS_REMOUNT_CMP_FLAGMASK ||
-	    data->rsize != nfss->rsize ||
-	    data->wsize != nfss->wsize ||
-	    data->version != nfss->nfs_client->rpc_ops->version ||
-	    data->minorversion != nfss->nfs_client->cl_minorversion ||
-	    data->retrans != nfss->client->cl_timeout->to_retries ||
-	    !nfs_auth_info_match(&data->auth_info, nfss->client->cl_auth->au_flavor) ||
-	    data->acregmin != nfss->acregmin / HZ ||
-	    data->acregmax != nfss->acregmax / HZ ||
-	    data->acdirmin != nfss->acdirmin / HZ ||
-	    data->acdirmax != nfss->acdirmax / HZ ||
-	    data->timeo != (10U * nfss->client->cl_timeout->to_initval / HZ) ||
-	    data->nfs_server.port != nfss->port ||
-	    data->nfs_server.addrlen != nfss->nfs_client->cl_addrlen ||
-	    !rpc_cmp_addr((struct sockaddr *)&data->nfs_server.address,
+	if ((cfg->flags ^ nfss->flags) & NFS_REMOUNT_CMP_FLAGMASK ||
+	    cfg->rsize != nfss->rsize ||
+	    cfg->wsize != nfss->wsize ||
+	    cfg->version != nfss->nfs_client->rpc_ops->version ||
+	    cfg->minorversion != nfss->nfs_client->cl_minorversion ||
+	    cfg->retrans != nfss->client->cl_timeout->to_retries ||
+	    !nfs_auth_info_match(&cfg->auth_info, nfss->client->cl_auth->au_flavor) ||
+	    cfg->acregmin != nfss->acregmin / HZ ||
+	    cfg->acregmax != nfss->acregmax / HZ ||
+	    cfg->acdirmin != nfss->acdirmin / HZ ||
+	    cfg->acdirmax != nfss->acdirmax / HZ ||
+	    cfg->timeo != (10U * nfss->client->cl_timeout->to_initval / HZ) ||
+	    cfg->nfs_server.port != nfss->port ||
+	    cfg->nfs_server.addrlen != nfss->nfs_client->cl_addrlen ||
+	    !rpc_cmp_addr((struct sockaddr *)&cfg->nfs_server.address,
 			  (struct sockaddr *)&nfss->nfs_client->cl_addr))
 		return -EINVAL;
 
@@ -951,7 +951,7 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)
 {
 	int error;
 	struct nfs_server *nfss = sb->s_fs_info;
-	struct nfs_parsed_mount_data *data;
+	struct nfs_sb_config *cfg;
 	struct nfs_mount_data *options = (struct nfs_mount_data *)raw_data;
 	struct nfs4_mount_data *options4 = (struct nfs4_mount_data *)raw_data;
 	u32 nfsvers = nfss->nfs_client->rpc_ops->version;
@@ -969,32 +969,32 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)
 					   options->version <= 6))))
 		return 0;
 
-	data = kzalloc(sizeof(*data), GFP_KERNEL);
-	if (data == NULL)
+	cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+	if (cfg == NULL)
 		return -ENOMEM;
 
 	/* fill out struct with values from existing mount */
-	data->flags = nfss->flags;
-	data->rsize = nfss->rsize;
-	data->wsize = nfss->wsize;
-	data->retrans = nfss->client->cl_timeout->to_retries;
-	data->selected_flavor = nfss->client->cl_auth->au_flavor;
-	data->acregmin = nfss->acregmin / HZ;
-	data->acregmax = nfss->acregmax / HZ;
-	data->acdirmin = nfss->acdirmin / HZ;
-	data->acdirmax = nfss->acdirmax / HZ;
-	data->timeo = 10U * nfss->client->cl_timeout->to_initval / HZ;
-	data->nfs_server.port = nfss->port;
-	data->nfs_server.addrlen = nfss->nfs_client->cl_addrlen;
-	data->version = nfsvers;
-	data->minorversion = nfss->nfs_client->cl_minorversion;
-	data->net = current->nsproxy->net_ns;
-	memcpy(&data->nfs_server.address, &nfss->nfs_client->cl_addr,
-		data->nfs_server.addrlen);
+	cfg->flags = nfss->flags;
+	cfg->rsize = nfss->rsize;
+	cfg->wsize = nfss->wsize;
+	cfg->retrans = nfss->client->cl_timeout->to_retries;
+	cfg->selected_flavor = nfss->client->cl_auth->au_flavor;
+	cfg->acregmin = nfss->acregmin / HZ;
+	cfg->acregmax = nfss->acregmax / HZ;
+	cfg->acdirmin = nfss->acdirmin / HZ;
+	cfg->acdirmax = nfss->acdirmax / HZ;
+	cfg->timeo = 10U * nfss->client->cl_timeout->to_initval / HZ;
+	cfg->nfs_server.port = nfss->port;
+	cfg->nfs_server.addrlen = nfss->nfs_client->cl_addrlen;
+	cfg->version = nfsvers;
+	cfg->minorversion = nfss->nfs_client->cl_minorversion;
+	cfg->net = current->nsproxy->net_ns;
+	memcpy(&cfg->nfs_server.address, &nfss->nfs_client->cl_addr,
+		cfg->nfs_server.addrlen);
 
 	/* overwrite those values with any that were specified */
 	error = -EINVAL;
-	if (!nfs_parse_mount_options((char *)options, data))
+	if (!nfs_parse_mount_options((char *)options, cfg))
 		goto out;
 
 	/*
@@ -1003,13 +1003,13 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)
 	 * will clear MS_SYNCHRONOUS if -o sync wasn't specified in the
 	 * remount options, so we have to explicitly reset it.
 	 */
-	if (data->flags & NFS_MOUNT_NOAC)
+	if (cfg->flags & NFS_MOUNT_NOAC)
 		*flags |= MS_SYNCHRONOUS;
 
 	/* compare new mount options with old ones */
-	error = nfs_compare_remount_data(nfss, data);
+	error = nfs_compare_remount_data(nfss, cfg);
 out:
-	kfree(data);
+	kfree(cfg);
 	return error;
 }
 EXPORT_SYMBOL_GPL(nfs_remount);
@@ -1039,15 +1039,15 @@ inline void nfs_initialise_sb(struct super_block *sb)
  */
 void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
 {
-	struct nfs_parsed_mount_data *data = mount_info->parsed;
+	struct nfs_sb_config *cfg = mount_info->cfg;
 	struct nfs_server *server = NFS_SB(sb);
 
 	sb->s_blocksize_bits = 0;
 	sb->s_blocksize = 0;
 	sb->s_xattr = server->nfs_client->cl_nfs_mod->xattr;
 	sb->s_op = server->nfs_client->cl_nfs_mod->sops;
-	if (data && data->bsize)
-		sb->s_blocksize = nfs_block_size(data->bsize, &sb->s_blocksize_bits);
+	if (cfg && cfg->bsize)
+		sb->s_blocksize = nfs_block_size(cfg->bsize, &sb->s_blocksize_bits);
 
 	if (server->nfs_client->rpc_ops->version != 2) {
 		/* The VFS shouldn't apply the umask to mode bits. We will do
@@ -1198,7 +1198,7 @@ static int nfs_compare_super(struct super_block *sb, void *data)
 
 #ifdef CONFIG_NFS_FSCACHE
 static void nfs_get_cache_cookie(struct super_block *sb,
-				 struct nfs_parsed_mount_data *parsed,
+				 struct nfs_sb_config *cfg,
 				 struct nfs_clone_mount *cloned)
 {
 	struct nfs_server *nfss = NFS_SB(sb);
@@ -1208,12 +1208,12 @@ static void nfs_get_cache_cookie(struct super_block *sb,
 	nfss->fscache_key = NULL;
 	nfss->fscache = NULL;
 
-	if (parsed) {
-		if (!(parsed->options & NFS_OPTION_FSCACHE))
+	if (cfg) {
+		if (!(cfg->options & NFS_OPTION_FSCACHE))
 			return;
-		if (parsed->fscache_uniq) {
-			uniq = parsed->fscache_uniq;
-			ulen = strlen(parsed->fscache_uniq);
+		if (cfg->fscache_uniq) {
+			uniq = cfg->fscache_uniq;
+			ulen = strlen(cfg->fscache_uniq);
 		}
 	} else if (cloned) {
 		struct nfs_server *mnt_s = NFS_SB(cloned->sb);
@@ -1230,7 +1230,7 @@ static void nfs_get_cache_cookie(struct super_block *sb,
 }
 #else
 static void nfs_get_cache_cookie(struct super_block *sb,
-				 struct nfs_parsed_mount_data *parsed,
+				 struct nfs_sb_config *parsed,
 				 struct nfs_clone_mount *cloned)
 {
 }
@@ -1244,7 +1244,7 @@ int nfs_set_sb_security(struct super_block *s, struct dentry *mntroot,
 	if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL)
 		kflags |= SECURITY_LSM_NATIVE_LABELS;
 
-	error = security_sb_set_mnt_opts(s, &mount_info->parsed->lsm_opts,
+	error = security_sb_set_mnt_opts(s, &mount_info->cfg->lsm_opts,
 						kflags, &kflags_out);
 	if (error)
 		goto err;
@@ -1316,7 +1316,7 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
 	if (!s->s_root) {
 		/* initial superblock/root creation */
 		mount_info->fill_super(s, mount_info);
-		nfs_get_cache_cookie(s, mount_info->parsed, mount_info->cloned);
+		nfs_get_cache_cookie(s, mount_info->cfg, mount_info->cloned);
 	}
 
 	mntroot = nfs_get_root(s, mount_info->mntfh, dev_name);
@@ -1356,21 +1356,21 @@ struct dentry *nfs_fs_mount(struct file_system_type *fs_type,
 	struct nfs_subversion *nfs_mod;
 	int error;
 
-	mount_info.parsed = nfs_alloc_parsed_mount_data();
+	mount_info.cfg = nfs_alloc_parsed_mount_data();
 	mount_info.mntfh = nfs_alloc_fhandle();
-	if (mount_info.parsed == NULL || mount_info.mntfh == NULL)
+	if (mount_info.cfg == NULL || mount_info.mntfh == NULL)
 		goto out;
 
 	/* Validate the mount data */
-	error = nfs_validate_mount_data(fs_type, raw_data, mount_info.parsed, mount_info.mntfh, dev_name);
+	error = nfs_validate_mount_data(fs_type, raw_data, mount_info.cfg, mount_info.mntfh, dev_name);
 	if (error == NFS_TEXT_DATA)
-		error = nfs_validate_text_mount_data(raw_data, mount_info.parsed, dev_name);
+		error = nfs_validate_text_mount_data(raw_data, mount_info.cfg, dev_name);
 	if (error < 0) {
 		mntroot = ERR_PTR(error);
 		goto out;
 	}
 
-	nfs_mod = get_nfs_version(mount_info.parsed->version);
+	nfs_mod = get_nfs_version(mount_info.cfg->version);
 	if (IS_ERR(nfs_mod)) {
 		mntroot = ERR_CAST(nfs_mod);
 		goto out;
@@ -1380,7 +1380,7 @@ struct dentry *nfs_fs_mount(struct file_system_type *fs_type,
 
 	put_nfs_version(nfs_mod);
 out:
-	nfs_free_parsed_mount_data(mount_info.parsed);
+	nfs_free_parsed_mount_data(mount_info.cfg);
 	nfs_free_fhandle(mount_info.mntfh);
 	return mntroot;
 }

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 19/23] NFS: Split nfs_parse_mount_options() [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (17 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 18/23] NFS: Rename struct nfs_parsed_mount_data to struct nfs_sb_config " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-22 15:54 ` [PATCH 20/23] NFS: Deindent nfs_sb_config_parse_option() " David Howells
                   ` (7 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Split nfs_parse_mount_options() to move the prologue, list-splitting and
epilogue into one function and the per-option processing into another.

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

 fs/nfs/internal.h |    3 +
 fs/nfs/mount.c    |  143 ++++++++++++++++++++++++++++++-----------------------
 2 files changed, 83 insertions(+), 63 deletions(-)

diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index af6ca28da48d..ed255241baa7 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -100,7 +100,10 @@ struct nfs_sb_config {
 	unsigned int		version;
 	unsigned int		minorversion;
 	char			*fscache_uniq;
+	unsigned short		protofamily;
+	unsigned short		mountfamily;
 	bool			need_mount;
+	bool			sloppy;
 
 	struct {
 		struct sockaddr_storage	address;
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index 8f22163a4cc7..99735a494d46 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -494,46 +494,18 @@ static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
 }
 
 /*
- * Error-check and convert a string of mount options from user space into
- * a data structure.  The whole mount string is processed; bad options are
- * skipped as they are encountered.  If there were no errors, return 1;
- * otherwise return 0 (zero).
+ * Parse a single mount option in "key[=val]" form.
  */
-int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
+static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 {
-	char *p, *string, *secdata;
-	int rc, sloppy = 0, invalid_option = 0;
-	unsigned short protofamily = AF_UNSPEC;
-	unsigned short mountfamily = AF_UNSPEC;
-
-	if (!raw) {
-		dfprintk(MOUNT, "NFS: mount options string was NULL.\n");
-		return 1;
-	}
-	dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw);
-
-	secdata = alloc_secdata();
-	if (!secdata)
-		goto out_nomem;
-
-	rc = security_sb_copy_data(raw, secdata);
-	if (rc)
-		goto out_security_failure;
-
-	rc = security_sb_parse_opts_str(secdata, &cfg->lsm_opts);
-	if (rc)
-		goto out_security_failure;
-
-	free_secdata(secdata);
+	char *string;
+	int rc;
 
-	while ((p = strsep(&raw, ",")) != NULL) {
+	{
 		substring_t args[MAX_OPT_ARGS];
 		unsigned long option;
 		int token;
 
-		if (!*p)
-			continue;
-
 		dfprintk(MOUNT, "NFS:   parsing nfs mount option '%s'\n", p);
 
 		token = match_token(p, nfs_mount_option_tokens, args);
@@ -737,7 +709,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 			if (!rc) {
 				dfprintk(MOUNT, "NFS:   unrecognized "
 						"security flavor\n");
-				return 0;
+				return -EINVAL;
 			}
 			break;
 		case Opt_proto:
@@ -747,22 +719,22 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 			token = match_token(string,
 					    nfs_xprt_protocol_tokens, args);
 
-			protofamily = AF_INET;
+			cfg->protofamily = AF_INET;
 			switch (token) {
 			case Opt_xprt_udp6:
-				protofamily = AF_INET6;
+				cfg->protofamily = AF_INET6;
 			case Opt_xprt_udp:
 				cfg->flags &= ~NFS_MOUNT_TCP;
 				cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
 				break;
 			case Opt_xprt_tcp6:
-				protofamily = AF_INET6;
+				cfg->protofamily = AF_INET6;
 			case Opt_xprt_tcp:
 				cfg->flags |= NFS_MOUNT_TCP;
 				cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
 				break;
 			case Opt_xprt_rdma6:
-				protofamily = AF_INET6;
+				cfg->protofamily = AF_INET6;
 			case Opt_xprt_rdma:
 				/* vector side protocols to TCP */
 				cfg->flags |= NFS_MOUNT_TCP;
@@ -773,7 +745,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 				dfprintk(MOUNT, "NFS:   unrecognized "
 						"transport protocol\n");
 				kfree(string);
-				return 0;
+				return -EINVAL;
 			}
 			kfree(string);
 			break;
@@ -785,15 +757,15 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 					    nfs_xprt_protocol_tokens, args);
 			kfree(string);
 
-			mountfamily = AF_INET;
+			cfg->mountfamily = AF_INET;
 			switch (token) {
 			case Opt_xprt_udp6:
-				mountfamily = AF_INET6;
+				cfg->mountfamily = AF_INET6;
 			case Opt_xprt_udp:
 				cfg->mount_server.protocol = XPRT_TRANSPORT_UDP;
 				break;
 			case Opt_xprt_tcp6:
-				mountfamily = AF_INET6;
+				cfg->mountfamily = AF_INET6;
 			case Opt_xprt_tcp:
 				cfg->mount_server.protocol = XPRT_TRANSPORT_TCP;
 				break;
@@ -801,7 +773,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 			default:
 				dfprintk(MOUNT, "NFS:   unrecognized "
 						"transport protocol\n");
-				return 0;
+				return -EINVAL;
 			}
 			break;
 		case Opt_addr:
@@ -860,7 +832,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 				default:
 					dfprintk(MOUNT, "NFS:   invalid "
 							"lookupcache argument\n");
-					return 0;
+					return -EINVAL;
 			};
 			break;
 		case Opt_fscache_uniq:
@@ -893,7 +865,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 			default:
 				dfprintk(MOUNT, "NFS:	invalid	"
 						"local_lock argument\n");
-				return 0;
+				return -EINVAL;
 			};
 			break;
 
@@ -901,7 +873,7 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 		 * Special options
 		 */
 		case Opt_sloppy:
-			sloppy = 1;
+			cfg->sloppy = 1;
 			dfprintk(MOUNT, "NFS:   relaxing parsing rules\n");
 			break;
 		case Opt_userspace:
@@ -911,12 +883,63 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 			break;
 
 		default:
-			invalid_option = 1;
 			dfprintk(MOUNT, "NFS:   unrecognized mount option "
 					"'%s'\n", p);
+			return -EINVAL;
 		}
 	}
 
+	return 0;
+
+out_invalid_address:
+	printk(KERN_INFO "NFS: bad IP address specified: %s\n", p);
+	return -EINVAL;
+out_invalid_value:
+	printk(KERN_INFO "NFS: bad mount option value specified: %s\n", p);
+	return -EINVAL;
+out_nomem:
+	printk(KERN_INFO "NFS: not enough memory to parse option\n");
+	return -ENOMEM;
+}
+
+/*
+ * Error-check and convert a string of mount options from user space into
+ * a data structure.  The whole mount string is processed; bad options are
+ * skipped as they are encountered.  If there were no errors, return 1;
+ * otherwise return 0 (zero).
+ */
+int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
+{
+	char *p, *secdata;
+	int rc, sloppy = 0, invalid_option = 0;
+
+	if (!raw) {
+		dfprintk(MOUNT, "NFS: mount options string was NULL.\n");
+		return 1;
+	}
+	dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw);
+
+	secdata = alloc_secdata();
+	if (!secdata)
+		goto out_nomem;
+
+	rc = security_sb_copy_data(raw, secdata);
+	if (rc)
+		goto out_security_failure;
+
+	rc = security_sb_parse_opts_str(secdata, &cfg->lsm_opts);
+	if (rc)
+		goto out_security_failure;
+
+	free_secdata(secdata);
+
+	while ((p = strsep(&raw, ",")) != NULL) {
+		if (!*p)
+			continue;
+		if (nfs_sb_config_parse_option(cfg, p) < 0)
+			invalid_option = true;
+	}
+
 	if (!sloppy && invalid_option)
 		return 0;
 
@@ -931,22 +954,26 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 	 * verify that any proto=/mountproto= options match the address
 	 * families in the addr=/mountaddr= options.
 	 */
-	if (protofamily != AF_UNSPEC &&
-	    protofamily != cfg->nfs_server.address.ss_family)
+	if (cfg->protofamily != AF_UNSPEC &&
+	    cfg->protofamily != cfg->nfs_server.address.ss_family)
 		goto out_proto_mismatch;
 
-	if (mountfamily != AF_UNSPEC) {
+	if (cfg->mountfamily != AF_UNSPEC) {
 		if (cfg->mount_server.addrlen) {
-			if (mountfamily != cfg->mount_server.address.ss_family)
+			if (cfg->mountfamily != cfg->mount_server.address.ss_family)
 				goto out_mountproto_mismatch;
 		} else {
-			if (mountfamily != cfg->nfs_server.address.ss_family)
+			if (cfg->mountfamily != cfg->nfs_server.address.ss_family)
 				goto out_mountproto_mismatch;
 		}
 	}
 
 	return 1;
 
+out_minorversion_mismatch:
+	printk(KERN_INFO "NFS: mount option vers=%u does not support "
+			 "minorversion=%u\n", cfg->version, cfg->minorversion);
+	return 0;
 out_mountproto_mismatch:
 	printk(KERN_INFO "NFS: mount server address does not match mountproto= "
 			 "option\n");
@@ -954,20 +981,10 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 out_proto_mismatch:
 	printk(KERN_INFO "NFS: server address does not match proto= option\n");
 	return 0;
-out_invalid_address:
-	printk(KERN_INFO "NFS: bad IP address specified: %s\n", p);
-	return 0;
-out_invalid_value:
-	printk(KERN_INFO "NFS: bad mount option value specified: %s\n", p);
-	return 0;
-out_minorversion_mismatch:
-	printk(KERN_INFO "NFS: mount option vers=%u does not support "
-			 "minorversion=%u\n", cfg->version, cfg->minorversion);
-	return 0;
 out_migration_misuse:
 	printk(KERN_INFO
 		"NFS: 'migration' not supported for this NFS version\n");
-	return 0;
+	return -EINVAL;
 out_nomem:
 	printk(KERN_INFO "NFS: not enough memory to parse option\n");
 	return 0;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 20/23] NFS: Deindent nfs_sb_config_parse_option() [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (18 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 19/23] NFS: Split nfs_parse_mount_options() " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-22 15:54 ` [PATCH 21/23] NFS: Add a small buffer in nfs_sb_config to avoid string dup " David Howells
                   ` (6 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Deindent nfs_sb_config_parse_option().

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

 fs/nfs/mount.c |  706 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 351 insertions(+), 355 deletions(-)

diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index 99735a494d46..8296f2f84dc7 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -498,395 +498,391 @@ static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
  */
 static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 {
+	substring_t args[MAX_OPT_ARGS];
+	unsigned long option;
 	char *string;
-	int rc;
-
-	{
-		substring_t args[MAX_OPT_ARGS];
-		unsigned long option;
-		int token;
+	int rc, token;
 
-		dfprintk(MOUNT, "NFS:   parsing nfs mount option '%s'\n", p);
+	dfprintk(MOUNT, "NFS:   parsing nfs mount option '%s'\n", p);
 
-		token = match_token(p, nfs_mount_option_tokens, args);
-		switch (token) {
+	token = match_token(p, nfs_mount_option_tokens, args);
+	switch (token) {
 
 		/*
 		 * boolean options:  foo/nofoo
 		 */
-		case Opt_soft:
-			cfg->flags |= NFS_MOUNT_SOFT;
-			break;
-		case Opt_hard:
-			cfg->flags &= ~NFS_MOUNT_SOFT;
-			break;
-		case Opt_posix:
-			cfg->flags |= NFS_MOUNT_POSIX;
-			break;
-		case Opt_noposix:
-			cfg->flags &= ~NFS_MOUNT_POSIX;
-			break;
-		case Opt_cto:
-			cfg->flags &= ~NFS_MOUNT_NOCTO;
-			break;
-		case Opt_nocto:
-			cfg->flags |= NFS_MOUNT_NOCTO;
-			break;
-		case Opt_ac:
-			cfg->flags &= ~NFS_MOUNT_NOAC;
-			break;
-		case Opt_noac:
-			cfg->flags |= NFS_MOUNT_NOAC;
-			break;
-		case Opt_lock:
-			cfg->flags &= ~NFS_MOUNT_NONLM;
-			cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
-					NFS_MOUNT_LOCAL_FCNTL);
-			break;
-		case Opt_nolock:
-			cfg->flags |= NFS_MOUNT_NONLM;
-			cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
-				       NFS_MOUNT_LOCAL_FCNTL);
-			break;
-		case Opt_udp:
-			cfg->flags &= ~NFS_MOUNT_TCP;
-			cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-			break;
-		case Opt_tcp:
-			cfg->flags |= NFS_MOUNT_TCP;
-			cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-			break;
-		case Opt_rdma:
-			cfg->flags |= NFS_MOUNT_TCP; /* for side protocols */
-			cfg->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
-			xprt_load_transport(p);
-			break;
-		case Opt_acl:
-			cfg->flags &= ~NFS_MOUNT_NOACL;
-			break;
-		case Opt_noacl:
-			cfg->flags |= NFS_MOUNT_NOACL;
-			break;
-		case Opt_rdirplus:
-			cfg->flags &= ~NFS_MOUNT_NORDIRPLUS;
-			break;
-		case Opt_nordirplus:
-			cfg->flags |= NFS_MOUNT_NORDIRPLUS;
-			break;
-		case Opt_sharecache:
-			cfg->flags &= ~NFS_MOUNT_UNSHARED;
-			break;
-		case Opt_nosharecache:
-			cfg->flags |= NFS_MOUNT_UNSHARED;
-			break;
-		case Opt_resvport:
-			cfg->flags &= ~NFS_MOUNT_NORESVPORT;
-			break;
-		case Opt_noresvport:
-			cfg->flags |= NFS_MOUNT_NORESVPORT;
-			break;
-		case Opt_fscache:
-			cfg->options |= NFS_OPTION_FSCACHE;
-			kfree(cfg->fscache_uniq);
-			cfg->fscache_uniq = NULL;
-			break;
-		case Opt_nofscache:
-			cfg->options &= ~NFS_OPTION_FSCACHE;
-			kfree(cfg->fscache_uniq);
-			cfg->fscache_uniq = NULL;
-			break;
-		case Opt_migration:
-			cfg->options |= NFS_OPTION_MIGRATION;
-			break;
-		case Opt_nomigration:
-			cfg->options &= NFS_OPTION_MIGRATION;
-			break;
+	case Opt_soft:
+		cfg->flags |= NFS_MOUNT_SOFT;
+		break;
+	case Opt_hard:
+		cfg->flags &= ~NFS_MOUNT_SOFT;
+		break;
+	case Opt_posix:
+		cfg->flags |= NFS_MOUNT_POSIX;
+		break;
+	case Opt_noposix:
+		cfg->flags &= ~NFS_MOUNT_POSIX;
+		break;
+	case Opt_cto:
+		cfg->flags &= ~NFS_MOUNT_NOCTO;
+		break;
+	case Opt_nocto:
+		cfg->flags |= NFS_MOUNT_NOCTO;
+		break;
+	case Opt_ac:
+		cfg->flags &= ~NFS_MOUNT_NOAC;
+		break;
+	case Opt_noac:
+		cfg->flags |= NFS_MOUNT_NOAC;
+		break;
+	case Opt_lock:
+		cfg->flags &= ~NFS_MOUNT_NONLM;
+		cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
+				NFS_MOUNT_LOCAL_FCNTL);
+		break;
+	case Opt_nolock:
+		cfg->flags |= NFS_MOUNT_NONLM;
+		cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
+			       NFS_MOUNT_LOCAL_FCNTL);
+		break;
+	case Opt_udp:
+		cfg->flags &= ~NFS_MOUNT_TCP;
+		cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
+		break;
+	case Opt_tcp:
+		cfg->flags |= NFS_MOUNT_TCP;
+		cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+		break;
+	case Opt_rdma:
+		cfg->flags |= NFS_MOUNT_TCP; /* for side protocols */
+		cfg->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
+		xprt_load_transport(p);
+		break;
+	case Opt_acl:
+		cfg->flags &= ~NFS_MOUNT_NOACL;
+		break;
+	case Opt_noacl:
+		cfg->flags |= NFS_MOUNT_NOACL;
+		break;
+	case Opt_rdirplus:
+		cfg->flags &= ~NFS_MOUNT_NORDIRPLUS;
+		break;
+	case Opt_nordirplus:
+		cfg->flags |= NFS_MOUNT_NORDIRPLUS;
+		break;
+	case Opt_sharecache:
+		cfg->flags &= ~NFS_MOUNT_UNSHARED;
+		break;
+	case Opt_nosharecache:
+		cfg->flags |= NFS_MOUNT_UNSHARED;
+		break;
+	case Opt_resvport:
+		cfg->flags &= ~NFS_MOUNT_NORESVPORT;
+		break;
+	case Opt_noresvport:
+		cfg->flags |= NFS_MOUNT_NORESVPORT;
+		break;
+	case Opt_fscache:
+		cfg->options |= NFS_OPTION_FSCACHE;
+		kfree(cfg->fscache_uniq);
+		cfg->fscache_uniq = NULL;
+		break;
+	case Opt_nofscache:
+		cfg->options &= ~NFS_OPTION_FSCACHE;
+		kfree(cfg->fscache_uniq);
+		cfg->fscache_uniq = NULL;
+		break;
+	case Opt_migration:
+		cfg->options |= NFS_OPTION_MIGRATION;
+		break;
+	case Opt_nomigration:
+		cfg->options &= NFS_OPTION_MIGRATION;
+		break;
 
 		/*
 		 * options that take numeric values
 		 */
-		case Opt_port:
-			if (nfs_get_option_ul(args, &option) ||
-			    option > USHRT_MAX)
-				goto out_invalid_value;
-			cfg->nfs_server.port = option;
-			break;
-		case Opt_rsize:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->rsize = option;
-			break;
-		case Opt_wsize:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->wsize = option;
-			break;
-		case Opt_bsize:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->bsize = option;
-			break;
-		case Opt_timeo:
-			if (nfs_get_option_ul_bound(args, &option, 1, INT_MAX))
-				goto out_invalid_value;
-			cfg->timeo = option;
-			break;
-		case Opt_retrans:
-			if (nfs_get_option_ul_bound(args, &option, 0, INT_MAX))
-				goto out_invalid_value;
-			cfg->retrans = option;
-			break;
-		case Opt_acregmin:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->acregmin = option;
-			break;
-		case Opt_acregmax:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->acregmax = option;
-			break;
-		case Opt_acdirmin:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->acdirmin = option;
-			break;
-		case Opt_acdirmax:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->acdirmax = option;
-			break;
-		case Opt_actimeo:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->acregmin = cfg->acregmax =
+	case Opt_port:
+		if (nfs_get_option_ul(args, &option) ||
+		    option > USHRT_MAX)
+			goto out_invalid_value;
+		cfg->nfs_server.port = option;
+		break;
+	case Opt_rsize:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->rsize = option;
+		break;
+	case Opt_wsize:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->wsize = option;
+		break;
+	case Opt_bsize:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->bsize = option;
+		break;
+	case Opt_timeo:
+		if (nfs_get_option_ul_bound(args, &option, 1, INT_MAX))
+			goto out_invalid_value;
+		cfg->timeo = option;
+		break;
+	case Opt_retrans:
+		if (nfs_get_option_ul_bound(args, &option, 0, INT_MAX))
+			goto out_invalid_value;
+		cfg->retrans = option;
+		break;
+	case Opt_acregmin:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->acregmin = option;
+		break;
+	case Opt_acregmax:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->acregmax = option;
+		break;
+	case Opt_acdirmin:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->acdirmin = option;
+		break;
+	case Opt_acdirmax:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->acdirmax = option;
+		break;
+	case Opt_actimeo:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->acregmin = cfg->acregmax =
 			cfg->acdirmin = cfg->acdirmax = option;
-			break;
-		case Opt_namelen:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			cfg->namlen = option;
-			break;
-		case Opt_mountport:
-			if (nfs_get_option_ul(args, &option) ||
-			    option > USHRT_MAX)
-				goto out_invalid_value;
-			cfg->mount_server.port = option;
-			break;
-		case Opt_mountvers:
-			if (nfs_get_option_ul(args, &option) ||
-			    option < NFS_MNT_VERSION ||
-			    option > NFS_MNT3_VERSION)
-				goto out_invalid_value;
-			cfg->mount_server.version = option;
-			break;
-		case Opt_minorversion:
-			if (nfs_get_option_ul(args, &option))
-				goto out_invalid_value;
-			if (option > NFS4_MAX_MINOR_VERSION)
-				goto out_invalid_value;
-			cfg->minorversion = option;
-			break;
+		break;
+	case Opt_namelen:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		cfg->namlen = option;
+		break;
+	case Opt_mountport:
+		if (nfs_get_option_ul(args, &option) ||
+		    option > USHRT_MAX)
+			goto out_invalid_value;
+		cfg->mount_server.port = option;
+		break;
+	case Opt_mountvers:
+		if (nfs_get_option_ul(args, &option) ||
+		    option < NFS_MNT_VERSION ||
+		    option > NFS_MNT3_VERSION)
+			goto out_invalid_value;
+		cfg->mount_server.version = option;
+		break;
+	case Opt_minorversion:
+		if (nfs_get_option_ul(args, &option))
+			goto out_invalid_value;
+		if (option > NFS4_MAX_MINOR_VERSION)
+			goto out_invalid_value;
+		cfg->minorversion = option;
+		break;
 
 		/*
 		 * options that take text values
 		 */
-		case Opt_nfsvers:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			rc = nfs_parse_version_string(string, cfg, args);
-			kfree(string);
-			if (!rc)
-				goto out_invalid_value;
+	case Opt_nfsvers:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		rc = nfs_parse_version_string(string, cfg, args);
+		kfree(string);
+		if (!rc)
+			goto out_invalid_value;
+		break;
+	case Opt_sec:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		rc = nfs_parse_security_flavors(string, cfg);
+		kfree(string);
+		if (!rc) {
+			dfprintk(MOUNT, "NFS:   unrecognized "
+				 "security flavor\n");
+			return -EINVAL;
+		}
+		break;
+	case Opt_proto:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		token = match_token(string,
+				    nfs_xprt_protocol_tokens, args);
+
+		cfg->protofamily = AF_INET;
+		switch (token) {
+		case Opt_xprt_udp6:
+			cfg->protofamily = AF_INET6;
+		case Opt_xprt_udp:
+			cfg->flags &= ~NFS_MOUNT_TCP;
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
 			break;
-		case Opt_sec:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			rc = nfs_parse_security_flavors(string, cfg);
-			kfree(string);
-			if (!rc) {
-				dfprintk(MOUNT, "NFS:   unrecognized "
-						"security flavor\n");
-				return -EINVAL;
-			}
+		case Opt_xprt_tcp6:
+			cfg->protofamily = AF_INET6;
+		case Opt_xprt_tcp:
+			cfg->flags |= NFS_MOUNT_TCP;
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
 			break;
-		case Opt_proto:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string,
-					    nfs_xprt_protocol_tokens, args);
-
-			cfg->protofamily = AF_INET;
-			switch (token) {
-			case Opt_xprt_udp6:
-				cfg->protofamily = AF_INET6;
-			case Opt_xprt_udp:
-				cfg->flags &= ~NFS_MOUNT_TCP;
-				cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
-				break;
-			case Opt_xprt_tcp6:
-				cfg->protofamily = AF_INET6;
-			case Opt_xprt_tcp:
-				cfg->flags |= NFS_MOUNT_TCP;
-				cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-				break;
-			case Opt_xprt_rdma6:
-				cfg->protofamily = AF_INET6;
-			case Opt_xprt_rdma:
-				/* vector side protocols to TCP */
-				cfg->flags |= NFS_MOUNT_TCP;
-				cfg->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
-				xprt_load_transport(string);
-				break;
-			default:
-				dfprintk(MOUNT, "NFS:   unrecognized "
-						"transport protocol\n");
-				kfree(string);
-				return -EINVAL;
-			}
-			kfree(string);
+		case Opt_xprt_rdma6:
+			cfg->protofamily = AF_INET6;
+		case Opt_xprt_rdma:
+			/* vector side protocols to TCP */
+			cfg->flags |= NFS_MOUNT_TCP;
+			cfg->nfs_server.protocol = XPRT_TRANSPORT_RDMA;
+			xprt_load_transport(string);
 			break;
-		case Opt_mountproto:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string,
-					    nfs_xprt_protocol_tokens, args);
+		default:
+			dfprintk(MOUNT, "NFS:   unrecognized "
+				 "transport protocol\n");
 			kfree(string);
+			return -EINVAL;
+		}
+		kfree(string);
+		break;
+	case Opt_mountproto:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		token = match_token(string,
+				    nfs_xprt_protocol_tokens, args);
+		kfree(string);
 
-			cfg->mountfamily = AF_INET;
-			switch (token) {
-			case Opt_xprt_udp6:
-				cfg->mountfamily = AF_INET6;
-			case Opt_xprt_udp:
-				cfg->mount_server.protocol = XPRT_TRANSPORT_UDP;
-				break;
-			case Opt_xprt_tcp6:
-				cfg->mountfamily = AF_INET6;
-			case Opt_xprt_tcp:
-				cfg->mount_server.protocol = XPRT_TRANSPORT_TCP;
-				break;
-			case Opt_xprt_rdma: /* not used for side protocols */
-			default:
-				dfprintk(MOUNT, "NFS:   unrecognized "
-						"transport protocol\n");
-				return -EINVAL;
-			}
+		cfg->mountfamily = AF_INET;
+		switch (token) {
+		case Opt_xprt_udp6:
+			cfg->mountfamily = AF_INET6;
+		case Opt_xprt_udp:
+			cfg->mount_server.protocol = XPRT_TRANSPORT_UDP;
 			break;
-		case Opt_addr:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			cfg->nfs_server.addrlen =
-				rpc_pton(cfg->net, string, strlen(string),
-					(struct sockaddr *)
-					&cfg->nfs_server.address,
-					sizeof(cfg->nfs_server.address));
-			kfree(string);
-			if (cfg->nfs_server.addrlen == 0)
-				goto out_invalid_address;
+		case Opt_xprt_tcp6:
+			cfg->mountfamily = AF_INET6;
+		case Opt_xprt_tcp:
+			cfg->mount_server.protocol = XPRT_TRANSPORT_TCP;
 			break;
-		case Opt_clientaddr:
-			if (nfs_get_option_str(args, &cfg->client_address))
-				goto out_nomem;
+		case Opt_xprt_rdma: /* not used for side protocols */
+		default:
+			dfprintk(MOUNT, "NFS:   unrecognized "
+				 "transport protocol\n");
+			return -EINVAL;
+		}
+		break;
+	case Opt_addr:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		cfg->nfs_server.addrlen =
+			rpc_pton(cfg->net, string, strlen(string),
+				 (struct sockaddr *)
+				 &cfg->nfs_server.address,
+				 sizeof(cfg->nfs_server.address));
+		kfree(string);
+		if (cfg->nfs_server.addrlen == 0)
+			goto out_invalid_address;
+		break;
+	case Opt_clientaddr:
+		if (nfs_get_option_str(args, &cfg->client_address))
+			goto out_nomem;
+		break;
+	case Opt_mounthost:
+		if (nfs_get_option_str(args,
+				       &cfg->mount_server.hostname))
+			goto out_nomem;
+		break;
+	case Opt_mountaddr:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		cfg->mount_server.addrlen =
+			rpc_pton(cfg->net, string, strlen(string),
+				 (struct sockaddr *)
+				 &cfg->mount_server.address,
+				 sizeof(cfg->mount_server.address));
+		kfree(string);
+		if (cfg->mount_server.addrlen == 0)
+			goto out_invalid_address;
+		break;
+	case Opt_lookupcache:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		token = match_token(string,
+				    nfs_lookupcache_tokens, args);
+		kfree(string);
+		switch (token) {
+		case Opt_lookupcache_all:
+			cfg->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
 			break;
-		case Opt_mounthost:
-			if (nfs_get_option_str(args,
-					       &cfg->mount_server.hostname))
-				goto out_nomem;
+		case Opt_lookupcache_positive:
+			cfg->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
+			cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
 			break;
-		case Opt_mountaddr:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			cfg->mount_server.addrlen =
-				rpc_pton(cfg->net, string, strlen(string),
-					(struct sockaddr *)
-					&cfg->mount_server.address,
-					sizeof(cfg->mount_server.address));
-			kfree(string);
-			if (cfg->mount_server.addrlen == 0)
-				goto out_invalid_address;
+		case Opt_lookupcache_none:
+			cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
 			break;
-		case Opt_lookupcache:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string,
-					nfs_lookupcache_tokens, args);
-			kfree(string);
-			switch (token) {
-				case Opt_lookupcache_all:
-					cfg->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
-					break;
-				case Opt_lookupcache_positive:
-					cfg->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
-					cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
-					break;
-				case Opt_lookupcache_none:
-					cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
-					break;
-				default:
-					dfprintk(MOUNT, "NFS:   invalid "
-							"lookupcache argument\n");
-					return -EINVAL;
-			};
+		default:
+			dfprintk(MOUNT, "NFS:   invalid "
+				 "lookupcache argument\n");
+			return -EINVAL;
+		};
+		break;
+	case Opt_fscache_uniq:
+		if (nfs_get_option_str(args, &cfg->fscache_uniq))
+			goto out_nomem;
+		cfg->options |= NFS_OPTION_FSCACHE;
+		break;
+	case Opt_local_lock:
+		string = match_strdup(args);
+		if (string == NULL)
+			goto out_nomem;
+		token = match_token(string, nfs_local_lock_tokens,
+				    args);
+		kfree(string);
+		switch (token) {
+		case Opt_local_lock_all:
+			cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
+				       NFS_MOUNT_LOCAL_FCNTL);
 			break;
-		case Opt_fscache_uniq:
-			if (nfs_get_option_str(args, &cfg->fscache_uniq))
-				goto out_nomem;
-			cfg->options |= NFS_OPTION_FSCACHE;
+		case Opt_local_lock_flock:
+			cfg->flags |= NFS_MOUNT_LOCAL_FLOCK;
 			break;
-		case Opt_local_lock:
-			string = match_strdup(args);
-			if (string == NULL)
-				goto out_nomem;
-			token = match_token(string, nfs_local_lock_tokens,
-					args);
-			kfree(string);
-			switch (token) {
-			case Opt_local_lock_all:
-				cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
-					       NFS_MOUNT_LOCAL_FCNTL);
-				break;
-			case Opt_local_lock_flock:
-				cfg->flags |= NFS_MOUNT_LOCAL_FLOCK;
-				break;
-			case Opt_local_lock_posix:
-				cfg->flags |= NFS_MOUNT_LOCAL_FCNTL;
-				break;
-			case Opt_local_lock_none:
-				cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
-						NFS_MOUNT_LOCAL_FCNTL);
-				break;
-			default:
-				dfprintk(MOUNT, "NFS:	invalid	"
-						"local_lock argument\n");
-				return -EINVAL;
-			};
+		case Opt_local_lock_posix:
+			cfg->flags |= NFS_MOUNT_LOCAL_FCNTL;
 			break;
+		case Opt_local_lock_none:
+			cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
+					NFS_MOUNT_LOCAL_FCNTL);
+			break;
+		default:
+			dfprintk(MOUNT, "NFS:	invalid	"
+				 "local_lock argument\n");
+			return -EINVAL;
+		};
+		break;
 
 		/*
 		 * Special options
 		 */
-		case Opt_sloppy:
-			cfg->sloppy = 1;
-			dfprintk(MOUNT, "NFS:   relaxing parsing rules\n");
-			break;
-		case Opt_userspace:
-		case Opt_deprecated:
-			dfprintk(MOUNT, "NFS:   ignoring mount option "
-					"'%s'\n", p);
-			break;
+	case Opt_sloppy:
+		cfg->sloppy = 1;
+		dfprintk(MOUNT, "NFS:   relaxing parsing rules\n");
+		break;
+	case Opt_userspace:
+	case Opt_deprecated:
+		dfprintk(MOUNT, "NFS:   ignoring mount option "
+			 "'%s'\n", p);
+		break;
 
-		default:
-			dfprintk(MOUNT, "NFS:   unrecognized mount option "
-					"'%s'\n", p);
-			return -EINVAL;
-		}
+	default:
+		dfprintk(MOUNT, "NFS:   unrecognized mount option "
+			 "'%s'\n", p);
+		return -EINVAL;
 	}
 
 	return 0;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 21/23] NFS: Add a small buffer in nfs_sb_config to avoid string dup [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (19 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 20/23] NFS: Deindent nfs_sb_config_parse_option() " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-22 15:54 ` [PATCH 22/23] NFS: Do some tidying of the parsing code " David Howells
                   ` (5 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Add a small buffer in nfs_sb_config to avoid string duplication when
parsing numbers.  Also make the parsing function wrapper place the parsed
integer directly in the appropriate nfs_sb_config struct member.

Sigend-off-by: David Howells <dhowells@redhat.com>
---

 fs/nfs/internal.h |    2 +
 fs/nfs/mount.c    |   83 +++++++++++++++++++----------------------------------
 2 files changed, 32 insertions(+), 53 deletions(-)

diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index ed255241baa7..52242240933f 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -125,6 +125,8 @@ struct nfs_sb_config {
 
 	struct security_mnt_opts lsm_opts;
 	struct net		*net;
+
+	char			buf[32];	/* Parse buffer */
 };
 
 /* mount_clnt.c */
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index 8296f2f84dc7..e5593070422d 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -466,27 +466,22 @@ static int nfs_get_option_str(substring_t args[], char **option)
 	return !*option;
 }
 
-static int nfs_get_option_ul(substring_t args[], unsigned long *option)
+static int nfs_get_option_ui(struct nfs_sb_config *cfg,
+			     substring_t args[], unsigned int *option)
 {
-	int rc;
-	char *string;
-
-	string = match_strdup(args);
-	if (string == NULL)
-		return -ENOMEM;
-	rc = kstrtoul(string, 10, option);
-	kfree(string);
-
-	return rc;
+	match_strlcpy(cfg->buf, args, sizeof(cfg->buf));
+	return kstrtouint(cfg->buf, 10, option);
 }
 
-static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
-		unsigned long l_bound, unsigned long u_bound)
+static int nfs_get_option_ui_bound(struct nfs_sb_config *cfg,
+				   substring_t args[], unsigned int *option,
+				   unsigned int l_bound, unsigned u_bound)
 {
 	int ret;
 
-	ret = nfs_get_option_ul(args, option);
-	if (ret != 0)
+	match_strlcpy(cfg->buf, args, sizeof(cfg->buf));
+	ret = kstrtouint(cfg->buf, 10, option);
+	if (ret < 0)
 		return ret;
 	if (*option < l_bound || *option > u_bound)
 		return -ERANGE;
@@ -499,7 +494,6 @@ static int nfs_get_option_ul_bound(substring_t args[], unsigned long *option,
 static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 {
 	substring_t args[MAX_OPT_ARGS];
-	unsigned long option;
 	char *string;
 	int rc, token;
 
@@ -507,7 +501,6 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 
 	token = match_token(p, nfs_mount_option_tokens, args);
 	switch (token) {
-
 		/*
 		 * boolean options:  foo/nofoo
 		 */
@@ -603,86 +596,70 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		 * options that take numeric values
 		 */
 	case Opt_port:
-		if (nfs_get_option_ul(args, &option) ||
-		    option > USHRT_MAX)
+		if (nfs_get_option_ui_bound(cfg, args, &cfg->nfs_server.port,
+					    0, USHRT_MAX))
 			goto out_invalid_value;
-		cfg->nfs_server.port = option;
 		break;
 	case Opt_rsize:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->rsize))
 			goto out_invalid_value;
-		cfg->rsize = option;
 		break;
 	case Opt_wsize:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->wsize))
 			goto out_invalid_value;
-		cfg->wsize = option;
 		break;
 	case Opt_bsize:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->bsize))
 			goto out_invalid_value;
-		cfg->bsize = option;
 		break;
 	case Opt_timeo:
-		if (nfs_get_option_ul_bound(args, &option, 1, INT_MAX))
+		if (nfs_get_option_ui_bound(cfg, args, &cfg->timeo, 1, INT_MAX))
 			goto out_invalid_value;
-		cfg->timeo = option;
 		break;
 	case Opt_retrans:
-		if (nfs_get_option_ul_bound(args, &option, 0, INT_MAX))
+		if (nfs_get_option_ui_bound(cfg, args, &cfg->retrans, 0, INT_MAX))
 			goto out_invalid_value;
-		cfg->retrans = option;
 		break;
 	case Opt_acregmin:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->acregmin))
 			goto out_invalid_value;
-		cfg->acregmin = option;
 		break;
 	case Opt_acregmax:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->acregmax))
 			goto out_invalid_value;
-		cfg->acregmax = option;
 		break;
 	case Opt_acdirmin:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->acdirmin))
 			goto out_invalid_value;
-		cfg->acdirmin = option;
 		break;
 	case Opt_acdirmax:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->acdirmax))
 			goto out_invalid_value;
-		cfg->acdirmax = option;
 		break;
 	case Opt_actimeo:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->acdirmax))
 			goto out_invalid_value;
 		cfg->acregmin = cfg->acregmax =
-			cfg->acdirmin = cfg->acdirmax = option;
+			cfg->acdirmin = cfg->acdirmax;
 		break;
 	case Opt_namelen:
-		if (nfs_get_option_ul(args, &option))
+		if (nfs_get_option_ui(cfg, args, &cfg->namlen))
 			goto out_invalid_value;
-		cfg->namlen = option;
 		break;
 	case Opt_mountport:
-		if (nfs_get_option_ul(args, &option) ||
-		    option > USHRT_MAX)
+		if (nfs_get_option_ui_bound(cfg, args, &cfg->mount_server.port,
+					    0, USHRT_MAX))
 			goto out_invalid_value;
-		cfg->mount_server.port = option;
 		break;
 	case Opt_mountvers:
-		if (nfs_get_option_ul(args, &option) ||
-		    option < NFS_MNT_VERSION ||
-		    option > NFS_MNT3_VERSION)
+		if (nfs_get_option_ui_bound(cfg, args, &cfg->mount_server.version,
+					    NFS_MNT_VERSION, NFS_MNT3_VERSION))
 			goto out_invalid_value;
-		cfg->mount_server.version = option;
 		break;
 	case Opt_minorversion:
-		if (nfs_get_option_ul(args, &option))
-			goto out_invalid_value;
-		if (option > NFS4_MAX_MINOR_VERSION)
+		if (nfs_get_option_ui_bound(cfg, args, &cfg->minorversion,
+					    0, NFS4_MAX_MINOR_VERSION))
 			goto out_invalid_value;
-		cfg->minorversion = option;
 		break;
 
 		/*

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 22/23] NFS: Do some tidying of the parsing code [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (20 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 21/23] NFS: Add a small buffer in nfs_sb_config to avoid string dup " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-22 15:54 ` [PATCH 23/23] NFS: Add sb_config support. " David Howells
                   ` (4 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Do some tidying of the parsing code, including:

 (*) Returning 0/error rather than true/false.

 (*) Putting the nfs_sb_config pointer first in some arg lists.

 (*) Unwrap some lines that will now fit on one line.

 (*) Provide unioned sockaddr/sockaddr_storage fields to avoid casts.

 (*) nfs_parse_devname() can paste its return values directly into the
     nfs_sb_config struct as that's where the caller puts them.

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

 fs/nfs/internal.h |   16 +++++--
 fs/nfs/mount.c    |  128 +++++++++++++++++++++++------------------------------
 fs/nfs/super.c    |    2 -
 3 files changed, 67 insertions(+), 79 deletions(-)

diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 52242240933f..79e77ff2061c 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -86,11 +86,11 @@ struct nfs_client_initdata {
  * In-kernel mount arguments
  */
 struct nfs_sb_config {
-	int			flags;
+	unsigned int		flags;		/* NFS{,4}_MOUNT_* flags */
 	unsigned int		rsize, wsize;
 	unsigned int		timeo, retrans;
-	unsigned int		acregmin, acregmax,
-				acdirmin, acdirmax;
+	unsigned int		acregmin, acregmax;
+	unsigned int		acdirmin, acdirmax;
 	unsigned int		namlen;
 	unsigned int		options;
 	unsigned int		bsize;
@@ -106,7 +106,10 @@ struct nfs_sb_config {
 	bool			sloppy;
 
 	struct {
-		struct sockaddr_storage	address;
+		union {
+			struct sockaddr	address;
+			struct sockaddr_storage	_address;
+		};
 		size_t			addrlen;
 		char			*hostname;
 		u32			version;
@@ -115,7 +118,10 @@ struct nfs_sb_config {
 	} mount_server;
 
 	struct {
-		struct sockaddr_storage	address;
+		union {
+			struct sockaddr	address;
+			struct sockaddr_storage	_address;
+		};
 		size_t			addrlen;
 		char			*hostname;
 		char			*export_path;
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index e5593070422d..025525c8eb90 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -341,8 +341,9 @@ static void nfs_set_mount_transport_protocol(struct nfs_sb_config *cfg)
  * Add 'flavor' to 'auth_info' if not already present.
  * Returns true if 'flavor' ends up in the list, false otherwise
  */
-static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
-			      rpc_authflavor_t flavor)
+static int nfs_auth_info_add(struct nfs_sb_config *cfg,
+			     struct nfs_auth_info *auth_info,
+			     rpc_authflavor_t flavor)
 {
 	unsigned int i;
 	unsigned int max_flavor_len = ARRAY_SIZE(auth_info->flavors);
@@ -350,26 +351,27 @@ static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
 	/* make sure this flavor isn't already in the list */
 	for (i = 0; i < auth_info->flavor_len; i++) {
 		if (flavor == auth_info->flavors[i])
-			return true;
+			return 0;
 	}
 
 	if (auth_info->flavor_len + 1 >= max_flavor_len) {
 		dfprintk(MOUNT, "NFS: too many sec= flavors\n");
-		return false;
+		return -EINVAL;
 	}
 
 	auth_info->flavors[auth_info->flavor_len++] = flavor;
-	return true;
+	return 0;
 }
 
 /*
  * Parse the value of the 'sec=' option.
  */
-static int nfs_parse_security_flavors(char *value, struct nfs_sb_config *cfg)
+static int nfs_parse_security_flavors(struct nfs_sb_config *cfg, char *value)
 {
 	substring_t args[MAX_OPT_ARGS];
 	rpc_authflavor_t pseudoflavor;
 	char *p;
+	int ret;
 
 	dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value);
 
@@ -411,19 +413,20 @@ static int nfs_parse_security_flavors(char *value, struct nfs_sb_config *cfg)
 		default:
 			dfprintk(MOUNT,
 				 "NFS: sec= option '%s' not recognized\n", p);
-			return 0;
+			return -EINVAL;
 		}
 
-		if (!nfs_auth_info_add(&cfg->auth_info, pseudoflavor))
-			return 0;
+		ret = nfs_auth_info_add(cfg, &cfg->auth_info, pseudoflavor);
+		if (ret < 0)
+			return ret;
 	}
 
-	return 1;
+	return 0;
 }
 
-static int nfs_parse_version_string(char *string,
-		struct nfs_sb_config *cfg,
-		substring_t *args)
+static int nfs_parse_version_string(struct nfs_sb_config *cfg,
+				    char *string,
+				    substring_t *args)
 {
 	cfg->flags &= ~NFS_MOUNT_VER3;
 	switch (match_token(string, nfs_vers_tokens, args)) {
@@ -454,9 +457,10 @@ static int nfs_parse_version_string(char *string,
 		cfg->minorversion = 2;
 		break;
 	default:
-		return 0;
+		dfprintk(MOUNT, "NFS:   Unsupported NFS version\n");
+		return -EINVAL;
 	}
-	return 1;
+	return 0;
 }
 
 static int nfs_get_option_str(substring_t args[], char **option)
@@ -495,7 +499,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 {
 	substring_t args[MAX_OPT_ARGS];
 	char *string;
-	int rc, token;
+	int ret, token;
 
 	dfprintk(MOUNT, "NFS:   parsing nfs mount option '%s'\n", p);
 
@@ -530,13 +534,11 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		break;
 	case Opt_lock:
 		cfg->flags &= ~NFS_MOUNT_NONLM;
-		cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
-				NFS_MOUNT_LOCAL_FCNTL);
+		cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK | NFS_MOUNT_LOCAL_FCNTL);
 		break;
 	case Opt_nolock:
 		cfg->flags |= NFS_MOUNT_NONLM;
-		cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK |
-			       NFS_MOUNT_LOCAL_FCNTL);
+		cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK | NFS_MOUNT_LOCAL_FCNTL);
 		break;
 	case Opt_udp:
 		cfg->flags &= ~NFS_MOUNT_TCP;
@@ -669,29 +671,25 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		string = match_strdup(args);
 		if (string == NULL)
 			goto out_nomem;
-		rc = nfs_parse_version_string(string, cfg, args);
+		ret = nfs_parse_version_string(cfg, string, args);
 		kfree(string);
-		if (!rc)
-			goto out_invalid_value;
+		if (ret < 0)
+			return ret;
 		break;
 	case Opt_sec:
 		string = match_strdup(args);
 		if (string == NULL)
 			goto out_nomem;
-		rc = nfs_parse_security_flavors(string, cfg);
+		ret = nfs_parse_security_flavors(cfg, string);
 		kfree(string);
-		if (!rc) {
-			dfprintk(MOUNT, "NFS:   unrecognized "
-				 "security flavor\n");
-			return -EINVAL;
-		}
+		if (ret < 0)
+			return ret;
 		break;
 	case Opt_proto:
 		string = match_strdup(args);
 		if (string == NULL)
 			goto out_nomem;
-		token = match_token(string,
-				    nfs_xprt_protocol_tokens, args);
+		token = match_token(string, nfs_xprt_protocol_tokens, args);
 
 		cfg->protofamily = AF_INET;
 		switch (token) {
@@ -716,9 +714,8 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			xprt_load_transport(string);
 			break;
 		default:
-			dfprintk(MOUNT, "NFS:   unrecognized "
-				 "transport protocol\n");
 			kfree(string);
+			dfprintk(MOUNT, "NFS:   unrecognized transport protocol\n");
 			return -EINVAL;
 		}
 		kfree(string);
@@ -727,8 +724,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		string = match_strdup(args);
 		if (string == NULL)
 			goto out_nomem;
-		token = match_token(string,
-				    nfs_xprt_protocol_tokens, args);
+		token = match_token(string, nfs_xprt_protocol_tokens, args);
 		kfree(string);
 
 		cfg->mountfamily = AF_INET;
@@ -745,8 +741,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			break;
 		case Opt_xprt_rdma: /* not used for side protocols */
 		default:
-			dfprintk(MOUNT, "NFS:   unrecognized "
-				 "transport protocol\n");
+			dfprintk(MOUNT, "NFS:   unrecognized transport protocol\n");
 			return -EINVAL;
 		}
 		break;
@@ -756,9 +751,8 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			goto out_nomem;
 		cfg->nfs_server.addrlen =
 			rpc_pton(cfg->net, string, strlen(string),
-				 (struct sockaddr *)
 				 &cfg->nfs_server.address,
-				 sizeof(cfg->nfs_server.address));
+				 sizeof(cfg->nfs_server._address));
 		kfree(string);
 		if (cfg->nfs_server.addrlen == 0)
 			goto out_invalid_address;
@@ -768,8 +762,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			goto out_nomem;
 		break;
 	case Opt_mounthost:
-		if (nfs_get_option_str(args,
-				       &cfg->mount_server.hostname))
+		if (nfs_get_option_str(args, &cfg->mount_server.hostname))
 			goto out_nomem;
 		break;
 	case Opt_mountaddr:
@@ -778,9 +771,8 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			goto out_nomem;
 		cfg->mount_server.addrlen =
 			rpc_pton(cfg->net, string, strlen(string),
-				 (struct sockaddr *)
 				 &cfg->mount_server.address,
-				 sizeof(cfg->mount_server.address));
+				 sizeof(cfg->mount_server._address));
 		kfree(string);
 		if (cfg->mount_server.addrlen == 0)
 			goto out_invalid_address;
@@ -789,8 +781,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		string = match_strdup(args);
 		if (string == NULL)
 			goto out_nomem;
-		token = match_token(string,
-				    nfs_lookupcache_tokens, args);
+		token = match_token(string, nfs_lookupcache_tokens, args);
 		kfree(string);
 		switch (token) {
 		case Opt_lookupcache_all:
@@ -804,10 +795,9 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
 			break;
 		default:
-			dfprintk(MOUNT, "NFS:   invalid "
-				 "lookupcache argument\n");
+			dfprintk(MOUNT, "NFS:   invalid lookupcache argument\n");
 			return -EINVAL;
-		};
+		}
 		break;
 	case Opt_fscache_uniq:
 		if (nfs_get_option_str(args, &cfg->fscache_uniq))
@@ -818,8 +808,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		string = match_strdup(args);
 		if (string == NULL)
 			goto out_nomem;
-		token = match_token(string, nfs_local_lock_tokens,
-				    args);
+		token = match_token(string, nfs_local_lock_tokens, args);
 		kfree(string);
 		switch (token) {
 		case Opt_local_lock_all:
@@ -837,8 +826,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 					NFS_MOUNT_LOCAL_FCNTL);
 			break;
 		default:
-			dfprintk(MOUNT, "NFS:	invalid	"
-				 "local_lock argument\n");
+			dfprintk(MOUNT, "NFS:	invalid	local_lock argument\n");
 			return -EINVAL;
 		};
 		break;
@@ -852,13 +840,11 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		break;
 	case Opt_userspace:
 	case Opt_deprecated:
-		dfprintk(MOUNT, "NFS:   ignoring mount option "
-			 "'%s'\n", p);
+		dfprintk(MOUNT, "NFS:   ignoring mount option '%s'\n", p);
 		break;
 
 	default:
-		dfprintk(MOUNT, "NFS:   unrecognized mount option "
-			 "'%s'\n", p);
+		dfprintk(MOUNT, "NFS:   unrecognized mount option '%s'\n", p);
 		return -EINVAL;
 	}
 
@@ -928,15 +914,15 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
 	 * families in the addr=/mountaddr= options.
 	 */
 	if (cfg->protofamily != AF_UNSPEC &&
-	    cfg->protofamily != cfg->nfs_server.address.ss_family)
+	    cfg->protofamily != cfg->nfs_server.address.sa_family)
 		goto out_proto_mismatch;
 
 	if (cfg->mountfamily != AF_UNSPEC) {
 		if (cfg->mount_server.addrlen) {
-			if (cfg->mountfamily != cfg->mount_server.address.ss_family)
+			if (cfg->mountfamily != cfg->mount_server.address.sa_family)
 				goto out_mountproto_mismatch;
 		} else {
-			if (cfg->mountfamily != cfg->nfs_server.address.ss_family)
+			if (cfg->mountfamily != cfg->nfs_server.address.sa_family)
 				goto out_mountproto_mismatch;
 		}
 	}
@@ -976,9 +962,9 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
  *
  * Note: caller frees hostname and export path, even on error.
  */
-static int nfs_parse_devname(const char *dev_name,
-			     char **hostname, size_t maxnamlen,
-			     char **export_path, size_t maxpathlen)
+static int nfs_parse_devname(struct nfs_sb_config *cfg,
+			     const char *dev_name,
+			     size_t maxnamlen, size_t maxpathlen)
 {
 	size_t len;
 	char *end;
@@ -1009,17 +995,17 @@ static int nfs_parse_devname(const char *dev_name,
 		goto out_hostname;
 
 	/* N.B. caller will free nfs_server.hostname in all cases */
-	*hostname = kstrndup(dev_name, len, GFP_KERNEL);
-	if (*hostname == NULL)
+	cfg->nfs_server.hostname = kmemdup_nul(dev_name, len, GFP_KERNEL);
+	if (!cfg->nfs_server.hostname)
 		goto out_nomem;
 	len = strlen(++end);
 	if (len > maxpathlen)
 		goto out_path;
-	*export_path = kstrndup(end, len, GFP_KERNEL);
-	if (!*export_path)
+	cfg->nfs_server.export_path = kmemdup_nul(end, len, GFP_KERNEL);
+	if (!cfg->nfs_server.export_path)
 		goto out_nomem;
 
-	dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", *export_path);
+	dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", cfg->nfs_server.export_path);
 	return 0;
 
 out_bad_devname:
@@ -1040,7 +1026,7 @@ static int nfs_parse_devname(const char *dev_name,
 }
 
 /*
- * Validate the NFS2/NFS3 mount data
+ * Parse monolithic NFS2/NFS3 mount data
  * - fills in the mount root filehandle
  *
  * For option strings, user space handles the following behaviors:
@@ -1365,11 +1351,7 @@ int nfs_validate_text_mount_data(void *options,
 
 	nfs_set_port(sap, &cfg->nfs_server.port, port);
 
-	return nfs_parse_devname(dev_name,
-				   &cfg->nfs_server.hostname,
-				   max_namelen,
-				   &cfg->nfs_server.export_path,
-				   max_pathlen);
+	return nfs_parse_devname(cfg, dev_name, max_namelen, max_pathlen);
 
 #if !IS_ENABLED(CONFIG_NFS_V4)
 out_v4_not_compiled:
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 26c1fca31e6b..acf935a6438d 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -795,7 +795,7 @@ static int nfs_request_mount(struct nfs_sb_config *cfg,
 	/*
 	 * Construct the mount server's address.
 	 */
-	if (cfg->mount_server.address.ss_family == AF_UNSPEC) {
+	if (cfg->mount_server.address.sa_family == AF_UNSPEC) {
 		memcpy(request.sap, &cfg->nfs_server.address,
 		       cfg->nfs_server.addrlen);
 		cfg->mount_server.addrlen = cfg->nfs_server.addrlen;

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH 23/23] NFS: Add sb_config support. [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (21 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 22/23] NFS: Do some tidying of the parsing code " David Howells
@ 2017-05-22 15:54 ` David Howells
  2017-05-30 14:50 ` [RFC][PATCH 00/23] VFS: Introduce superblock configuration context " Al Viro
                   ` (3 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-05-22 15:54 UTC (permalink / raw)
  To: mszeredi, viro, jlayton; +Cc: dhowells, linux-fsdevel, linux-nfs, linux-kernel

Add superblock configuration context support to NFS, parsing the options in
advance and attaching the information to struct nfs_sb_config.  The
highlights are:

 (*) Merge nfs_mount_info and nfs_clone_mount into nfs_sb_config.  This
     structure represents NFS's superblock config.

 (*) Make use of the VFS's parsing support to split comma-separated lists.

 (*) Pin the NFS protocol module in the nfs_sb_config.

 (*) Attach supplementary error information to sb_config.  This has the
     downside that these strings must be static and can't be formatted.

 (*) Remove the auxiliary file_system_type structs since the information
     necessary can be conveyed in the nfs_sb_config struct instead.

 (*) Root mounts are made by duplicating the config for the requested mount
     so as to have the same parameters.  Submounts pick up their parameters
     from the parent superblock.

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

 fs/nfs/client.c         |   18 +
 fs/nfs/getroot.c        |   72 +++--
 fs/nfs/internal.h       |  105 +++-----
 fs/nfs/mount.c          |  652 ++++++++++++++++++++++++++++-------------------
 fs/nfs/namespace.c      |   71 ++++-
 fs/nfs/nfs3_fs.h        |    2 
 fs/nfs/nfs3client.c     |    6 
 fs/nfs/nfs3proc.c       |    2 
 fs/nfs/nfs4_fs.h        |    4 
 fs/nfs/nfs4client.c     |   44 ++-
 fs/nfs/nfs4namespace.c  |  208 +++++++++------
 fs/nfs/nfs4proc.c       |    3 
 fs/nfs/nfs4super.c      |  220 ++++++++--------
 fs/nfs/proc.c           |    2 
 fs/nfs/super.c          |  296 +++++----------------
 include/linux/nfs_xdr.h |    7 -
 16 files changed, 875 insertions(+), 837 deletions(-)

diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index 5701f5122a64..acaab9ca0243 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -635,17 +635,16 @@ EXPORT_SYMBOL_GPL(nfs_init_client);
  * Create a version 2 or 3 client
  */
 static int nfs_init_server(struct nfs_server *server,
-			   const struct nfs_sb_config *cfg,
-			   struct nfs_subversion *nfs_mod)
+			   const struct nfs_sb_config *cfg)
 {
 	struct rpc_timeout timeparms;
 	struct nfs_client_initdata cl_init = {
 		.hostname = cfg->nfs_server.hostname,
 		.addr = (const struct sockaddr *)&cfg->nfs_server.address,
 		.addrlen = cfg->nfs_server.addrlen,
-		.nfs_mod = nfs_mod,
+		.nfs_mod = cfg->nfs_mod,
 		.proto = cfg->nfs_server.protocol,
-		.net = cfg->net,
+		.net = cfg->sc.net_ns,
 		.timeparms = &timeparms,
 	};
 	struct nfs_client *clp;
@@ -921,8 +920,7 @@ EXPORT_SYMBOL_GPL(nfs_free_server);
  * Create a version 2 or 3 volume record
  * - keyed on server and FSID
  */
-struct nfs_server *nfs_create_server(struct nfs_mount_info *mount_info,
-				     struct nfs_subversion *nfs_mod)
+struct nfs_server *nfs_create_server(struct nfs_sb_config *cfg)
 {
 	struct nfs_server *server;
 	struct nfs_fattr *fattr;
@@ -938,18 +936,18 @@ struct nfs_server *nfs_create_server(struct nfs_mount_info *mount_info,
 		goto error;
 
 	/* Get a client representation */
-	error = nfs_init_server(server, mount_info->cfg, nfs_mod);
+	error = nfs_init_server(server, cfg);
 	if (error < 0)
 		goto error;
 
 	/* Probe the root fh to retrieve its FSID */
-	error = nfs_probe_fsinfo(server, mount_info->mntfh, fattr);
+	error = nfs_probe_fsinfo(server, cfg->mntfh, fattr);
 	if (error < 0)
 		goto error;
 	if (server->nfs_client->rpc_ops->version == 3) {
 		if (server->namelen == 0 || server->namelen > NFS3_MAXNAMLEN)
 			server->namelen = NFS3_MAXNAMLEN;
-		if (!(mount_info->cfg->flags & NFS_MOUNT_NORDIRPLUS))
+		if (!(cfg->flags & NFS_MOUNT_NORDIRPLUS))
 			server->caps |= NFS_CAP_READDIRPLUS;
 	} else {
 		if (server->namelen == 0 || server->namelen > NFS2_MAXNAMLEN)
@@ -957,7 +955,7 @@ struct nfs_server *nfs_create_server(struct nfs_mount_info *mount_info,
 	}
 
 	if (!(fattr->valid & NFS_ATTR_FATTR)) {
-		error = nfs_mod->rpc_ops->getattr(server, mount_info->mntfh, fattr, NULL);
+		error = cfg->nfs_mod->rpc_ops->getattr(server, cfg->mntfh, fattr, NULL);
 		if (error < 0) {
 			dprintk("nfs_create_server: getattr error = %d\n", -error);
 			goto error;
diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
index 391dafaf9182..1b8717f53a4e 100644
--- a/fs/nfs/getroot.c
+++ b/fs/nfs/getroot.c
@@ -68,66 +68,70 @@ static int nfs_superblock_set_dummy_root(struct super_block *sb, struct inode *i
 /*
  * get an NFS2/NFS3 root dentry from the root filehandle
  */
-struct dentry *nfs_get_root(struct super_block *sb, struct nfs_fh *mntfh,
-			    const char *devname)
+int nfs_get_root(struct super_block *s, struct nfs_sb_config *cfg)
 {
-	struct nfs_server *server = NFS_SB(sb);
+	struct nfs_server *server = NFS_SB(s);
 	struct nfs_fsinfo fsinfo;
-	struct dentry *ret;
+	struct dentry *root;
 	struct inode *inode;
-	void *name = kstrdup(devname, GFP_KERNEL);
-	int error;
+	char *name;
+	int error = -ENOMEM;
 
+	name = kstrdup(cfg->sc.device, GFP_KERNEL);
 	if (!name)
-		return ERR_PTR(-ENOMEM);
+		goto out;
 
 	/* get the actual root for this mount */
 	fsinfo.fattr = nfs_alloc_fattr();
-	if (fsinfo.fattr == NULL) {
-		kfree(name);
-		return ERR_PTR(-ENOMEM);
-	}
+	if (fsinfo.fattr == NULL)
+		goto out_name;
 
-	error = server->nfs_client->rpc_ops->getroot(server, mntfh, &fsinfo);
+	error = server->nfs_client->rpc_ops->getroot(server, cfg->mntfh, &fsinfo);
 	if (error < 0) {
 		dprintk("nfs_get_root: getattr error = %d\n", -error);
-		ret = ERR_PTR(error);
-		goto out;
+		errorf("NFS: Couldn't getattr on root");
+		goto out_fattr;
 	}
 
-	inode = nfs_fhget(sb, mntfh, fsinfo.fattr, NULL);
+	inode = nfs_fhget(s, cfg->mntfh, fsinfo.fattr, NULL);
 	if (IS_ERR(inode)) {
 		dprintk("nfs_get_root: get root inode failed\n");
-		ret = ERR_CAST(inode);
-		goto out;
+		error = PTR_ERR(inode);
+		errorf("NFS: Couldn't get root inode");
+		goto out_fattr;
 	}
 
-	error = nfs_superblock_set_dummy_root(sb, inode);
-	if (error != 0) {
-		ret = ERR_PTR(error);
-		goto out;
-	}
+	error = nfs_superblock_set_dummy_root(s, inode);
+	if (error != 0)
+		goto out_fattr;
 
 	/* root dentries normally start off anonymous and get spliced in later
 	 * if the dentry tree reaches them; however if the dentry already
 	 * exists, we'll pick it up at this point and use it as the root
 	 */
-	ret = d_obtain_root(inode);
-	if (IS_ERR(ret)) {
+	root = d_obtain_root(inode);
+	if (IS_ERR(root)) {
 		dprintk("nfs_get_root: get root dentry failed\n");
-		goto out;
+		error = PTR_ERR(root);
+		errorf("NFS: Couldn't get root dentry");
+		goto out_fattr;
 	}
 
-	security_d_instantiate(ret, inode);
-	spin_lock(&ret->d_lock);
-	if (IS_ROOT(ret) && !ret->d_fsdata &&
-	    !(ret->d_flags & DCACHE_NFSFS_RENAMED)) {
-		ret->d_fsdata = name;
+	security_d_instantiate(root, inode);
+	spin_lock(&root->d_lock);
+	if (IS_ROOT(root) && !root->d_fsdata &&
+	    !(root->d_flags & DCACHE_NFSFS_RENAMED)) {
+		root->d_fsdata = name;
 		name = NULL;
 	}
-	spin_unlock(&ret->d_lock);
-out:
-	kfree(name);
+	spin_unlock(&root->d_lock);
+	cfg->sc.root = root;
+	error = 0;
+
+out_fattr:
 	nfs_free_fattr(fsinfo.fattr);
-	return ret;
+out_name:
+	kfree(name);
+out:
+	return error;
 }
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 79e77ff2061c..7acca9f53bcd 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -3,7 +3,7 @@
  */
 
 #include "nfs4_fs.h"
-#include <linux/mount.h>
+#include <linux/sb_config.h>
 #include <linux/security.h>
 #include <linux/crc32.h>
 #include <linux/sunrpc/addr.h>
@@ -36,18 +36,6 @@ static inline int nfs_attr_use_mounted_on_fileid(struct nfs_fattr *fattr)
 	return 1;
 }
 
-struct nfs_clone_mount {
-	const struct super_block *sb;
-	const struct dentry *dentry;
-	struct nfs_fh *fh;
-	struct nfs_fattr *fattr;
-	char *hostname;
-	char *mnt_path;
-	struct sockaddr *addr;
-	size_t addrlen;
-	rpc_authflavor_t authflavor;
-};
-
 /*
  * Note: RFC 1813 doesn't limit the number of auth flavors that
  * a server can return, so make something up.
@@ -82,10 +70,22 @@ struct nfs_client_initdata {
 	const struct rpc_timeout *timeparms;
 };
 
+enum nfs_mount_type {
+	NFS_MOUNT_ORDINARY,
+	NFS_MOUNT_CROSS_DEV,
+	NFS4_MOUNT_REMOTE,
+	NFS4_MOUNT_REFERRAL,
+	NFS4_MOUNT_REMOTE_REFERRAL,
+};
+
 /*
  * In-kernel mount arguments
  */
 struct nfs_sb_config {
+	struct sb_config	sc;
+	enum nfs_mount_type	mount_type : 8;
+	bool			skip_remount_option_check;
+	bool			need_mount;
 	unsigned int		flags;		/* NFS{,4}_MOUNT_* flags */
 	unsigned int		rsize, wsize;
 	unsigned int		timeo, retrans;
@@ -102,8 +102,6 @@ struct nfs_sb_config {
 	char			*fscache_uniq;
 	unsigned short		protofamily;
 	unsigned short		mountfamily;
-	bool			need_mount;
-	bool			sloppy;
 
 	struct {
 		union {
@@ -127,10 +125,21 @@ struct nfs_sb_config {
 		char			*export_path;
 		int			port;
 		unsigned short		protocol;
+		unsigned short		export_path_len;
 	} nfs_server;
 
-	struct security_mnt_opts lsm_opts;
-	struct net		*net;
+	struct nfs_fh		*mntfh;
+	struct nfs_subversion	*nfs_mod;
+
+	int (*set_security)(struct super_block *, struct nfs_sb_config *);
+
+	/* Information for a cloned mount. */
+	struct nfs_clone_mount {
+		struct super_block	*sb;
+		struct dentry		*dentry;
+		struct nfs_fattr	*fattr;
+		bool			cloned;
+	} clone_data;
 
 	char			buf[32];	/* Parse buffer */
 };
@@ -150,14 +159,6 @@ struct nfs_mount_request {
 	struct net		*net;
 };
 
-struct nfs_mount_info {
-	void (*fill_super)(struct super_block *, struct nfs_mount_info *);
-	int (*set_security)(struct super_block *, struct dentry *, struct nfs_mount_info *);
-	struct nfs_sb_config *cfg;
-	struct nfs_clone_mount *cloned;
-	struct nfs_fh *mntfh;
-};
-
 extern int nfs_mount(struct nfs_mount_request *info);
 extern void nfs_umount(const struct nfs_mount_request *info);
 
@@ -183,13 +184,9 @@ extern struct nfs_client *nfs4_find_client_ident(struct net *, int);
 extern struct nfs_client *
 nfs4_find_client_sessionid(struct net *, const struct sockaddr *,
 				struct nfs4_sessionid *, u32);
-extern struct nfs_server *nfs_create_server(struct nfs_mount_info *,
-					struct nfs_subversion *);
-extern struct nfs_server *nfs4_create_server(
-					struct nfs_mount_info *,
-					struct nfs_subversion *);
-extern struct nfs_server *nfs4_create_referral_server(struct nfs_clone_mount *,
-						      struct nfs_fh *);
+extern struct nfs_server *nfs_create_server(struct nfs_sb_config *);
+extern struct nfs_server *nfs4_create_server(struct nfs_sb_config *);
+extern struct nfs_server *nfs4_create_referral_server(struct nfs_sb_config *);
 extern int nfs4_update_server(struct nfs_server *server, const char *hostname,
 					struct sockaddr *sap, size_t salen,
 					struct net *net);
@@ -243,19 +240,7 @@ extern struct svc_version nfs4_callback_version4;
 struct nfs_pageio_descriptor;
 
 /* mount.c */
-#define NFS_TEXT_DATA		1
-
-extern struct nfs_sb_config *nfs_alloc_parsed_mount_data(void);
-extern void nfs_free_parsed_mount_data(struct nfs_sb_config *cfg);
-extern int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg);
-extern int nfs_validate_mount_data(struct file_system_type *fs_type,
-				   void *options,
-				   struct nfs_sb_config *cfg,
-				   struct nfs_fh *mntfh,
-				   const char *dev_name);
-extern int nfs_validate_text_mount_data(void *options,
-					struct nfs_sb_config *cfg,
-					const char *dev_name);
+extern const char nfs_slash[];
 
 /* pagelist.c */
 extern int __init nfs_init_nfspagecache(void);
@@ -418,24 +403,12 @@ extern int nfs_wait_atomic_killable(atomic_t *p);
 /* super.c */
 extern const struct super_operations nfs_sops;
 extern struct file_system_type nfs_fs_type;
-extern struct file_system_type nfs_xdev_fs_type;
-#if IS_ENABLED(CONFIG_NFS_V4)
-extern struct file_system_type nfs4_xdev_fs_type;
-extern struct file_system_type nfs4_referral_fs_type;
-#endif
 bool nfs_auth_info_match(const struct nfs_auth_info *, rpc_authflavor_t);
-struct dentry *nfs_try_mount(int, const char *, struct nfs_mount_info *,
-			struct nfs_subversion *);
-void nfs_initialise_sb(struct super_block *);
-int nfs_set_sb_security(struct super_block *, struct dentry *, struct nfs_mount_info *);
-int nfs_clone_sb_security(struct super_block *, struct dentry *, struct nfs_mount_info *);
-struct dentry *nfs_fs_mount_common(struct nfs_server *, int, const char *,
-				   struct nfs_mount_info *, struct nfs_subversion *);
-struct dentry *nfs_fs_mount(struct file_system_type *, int, const char *, void *);
-struct dentry * nfs_xdev_mount_common(struct file_system_type *, int,
-		const char *, struct nfs_mount_info *);
+int nfs_try_get_tree(struct nfs_sb_config *);
+int nfs_set_sb_security(struct super_block *, struct nfs_sb_config *);
+int nfs_clone_sb_security(struct super_block *, struct nfs_sb_config *);
+int nfs_get_tree_common(struct nfs_server *, struct nfs_sb_config *);
 void nfs_kill_super(struct super_block *);
-void nfs_fill_super(struct super_block *, struct nfs_mount_info *);
 
 extern struct rpc_stat nfs_rpcstat;
 
@@ -468,12 +441,9 @@ struct vfsmount *nfs_do_submount(struct dentry *, struct nfs_fh *,
 				 struct nfs_fattr *, rpc_authflavor_t);
 
 /* getroot.c */
-extern struct dentry *nfs_get_root(struct super_block *, struct nfs_fh *,
-				   const char *);
+extern int nfs_get_root(struct super_block *s, struct nfs_sb_config *cfg);
 #if IS_ENABLED(CONFIG_NFS_V4)
-extern struct dentry *nfs4_get_root(struct super_block *, struct nfs_fh *,
-				    const char *);
-
+extern int nfs4_get_root(struct super_block *s, struct nfs_sb_config *cfg);
 extern int nfs4_get_rootfh(struct nfs_server *server, struct nfs_fh *mntfh, bool);
 #endif
 
@@ -486,14 +456,13 @@ extern void nfs_read_prepare(struct rpc_task *task, void *calldata);
 extern void nfs_pageio_reset_read_mds(struct nfs_pageio_descriptor *pgio);
 
 /* super.c */
-void nfs_clone_super(struct super_block *, struct nfs_mount_info *);
 void nfs_umount_begin(struct super_block *);
 int  nfs_statfs(struct dentry *, struct kstatfs *);
 int  nfs_show_options(struct seq_file *, struct dentry *);
 int  nfs_show_devname(struct seq_file *, struct dentry *);
 int  nfs_show_path(struct seq_file *, struct dentry *);
 int  nfs_show_stats(struct seq_file *, struct dentry *);
-int nfs_remount(struct super_block *sb, int *flags, char *raw_data);
+int nfs_remount(struct super_block *sb, struct sb_config *sc);
 
 /* write.c */
 extern void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio,
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index 025525c8eb90..4ab3338b2208 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -240,42 +240,8 @@ static const match_table_t nfs_vers_tokens = {
 	{ Opt_vers_err, NULL }
 };
 
-struct nfs_sb_config *nfs_alloc_parsed_mount_data(void)
-{
-	struct nfs_sb_config *cfg;
-
-	cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
-	if (cfg) {
-		cfg->timeo		= NFS_UNSPEC_TIMEO;
-		cfg->retrans		= NFS_UNSPEC_RETRANS;
-		cfg->acregmin		= NFS_DEF_ACREGMIN;
-		cfg->acregmax		= NFS_DEF_ACREGMAX;
-		cfg->acdirmin		= NFS_DEF_ACDIRMIN;
-		cfg->acdirmax		= NFS_DEF_ACDIRMAX;
-		cfg->mount_server.port	= NFS_UNSPEC_PORT;
-		cfg->nfs_server.port	= NFS_UNSPEC_PORT;
-		cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
-		cfg->selected_flavor	= RPC_AUTH_MAXFLAVOR;
-		cfg->minorversion	= 0;
-		cfg->need_mount	= true;
-		cfg->net		= current->nsproxy->net_ns;
-		security_init_mnt_opts(&cfg->lsm_opts);
-	}
-	return cfg;
-}
-
-void nfs_free_parsed_mount_data(struct nfs_sb_config *cfg)
-{
-	if (cfg) {
-		kfree(cfg->client_address);
-		kfree(cfg->mount_server.hostname);
-		kfree(cfg->nfs_server.export_path);
-		kfree(cfg->nfs_server.hostname);
-		kfree(cfg->fscache_uniq);
-		security_free_mnt_opts(&cfg->lsm_opts);
-		kfree(cfg);
-	}
-}
+const char nfs_slash[] = "/";
+EXPORT_SYMBOL_GPL(nfs_slash);
 
 /*
  * Sanity-check a server address provided by the mount command.
@@ -354,10 +320,8 @@ static int nfs_auth_info_add(struct nfs_sb_config *cfg,
 			return 0;
 	}
 
-	if (auth_info->flavor_len + 1 >= max_flavor_len) {
-		dfprintk(MOUNT, "NFS: too many sec= flavors\n");
-		return -EINVAL;
-	}
+	if (auth_info->flavor_len + 1 >= max_flavor_len)
+		return invalf("NFS: too many sec= flavors");
 
 	auth_info->flavors[auth_info->flavor_len++] = flavor;
 	return 0;
@@ -411,9 +375,7 @@ static int nfs_parse_security_flavors(struct nfs_sb_config *cfg, char *value)
 			pseudoflavor = RPC_AUTH_GSS_SPKMP;
 			break;
 		default:
-			dfprintk(MOUNT,
-				 "NFS: sec= option '%s' not recognized\n", p);
-			return -EINVAL;
+			return invalf("NFS: sec=%s option not recognized", p);
 		}
 
 		ret = nfs_auth_info_add(cfg, &cfg->auth_info, pseudoflavor);
@@ -457,8 +419,7 @@ static int nfs_parse_version_string(struct nfs_sb_config *cfg,
 		cfg->minorversion = 2;
 		break;
 	default:
-		dfprintk(MOUNT, "NFS:   Unsupported NFS version\n");
-		return -EINVAL;
+		return invalf("NFS: Unsupported NFS version");
 	}
 	return 0;
 }
@@ -495,8 +456,9 @@ static int nfs_get_option_ui_bound(struct nfs_sb_config *cfg,
 /*
  * Parse a single mount option in "key[=val]" form.
  */
-static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
+static int nfs_sb_config_parse_option(struct sb_config *sc, char *p)
 {
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
 	substring_t args[MAX_OPT_ARGS];
 	char *string;
 	int ret, token;
@@ -715,8 +677,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			break;
 		default:
 			kfree(string);
-			dfprintk(MOUNT, "NFS:   unrecognized transport protocol\n");
-			return -EINVAL;
+			return invalf("NFS: Unrecognized transport protocol");
 		}
 		kfree(string);
 		break;
@@ -741,8 +702,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			break;
 		case Opt_xprt_rdma: /* not used for side protocols */
 		default:
-			dfprintk(MOUNT, "NFS:   unrecognized transport protocol\n");
-			return -EINVAL;
+			return invalf("NFS: Unrecognized transport protocol");
 		}
 		break;
 	case Opt_addr:
@@ -750,7 +710,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		if (string == NULL)
 			goto out_nomem;
 		cfg->nfs_server.addrlen =
-			rpc_pton(cfg->net, string, strlen(string),
+			rpc_pton(sc->net_ns, string, strlen(string),
 				 &cfg->nfs_server.address,
 				 sizeof(cfg->nfs_server._address));
 		kfree(string);
@@ -770,7 +730,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 		if (string == NULL)
 			goto out_nomem;
 		cfg->mount_server.addrlen =
-			rpc_pton(cfg->net, string, strlen(string),
+			rpc_pton(sc->net_ns, string, strlen(string),
 				 &cfg->mount_server.address,
 				 sizeof(cfg->mount_server._address));
 		kfree(string);
@@ -795,8 +755,7 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 			cfg->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
 			break;
 		default:
-			dfprintk(MOUNT, "NFS:   invalid lookupcache argument\n");
-			return -EINVAL;
+			return invalf("NFS: Invalid lookupcache argument");
 		}
 		break;
 	case Opt_fscache_uniq:
@@ -826,16 +785,15 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 					NFS_MOUNT_LOCAL_FCNTL);
 			break;
 		default:
-			dfprintk(MOUNT, "NFS:	invalid	local_lock argument\n");
-			return -EINVAL;
-		};
+			return invalf("NFS: invalid local_lock argument");
+		}
 		break;
 
 		/*
 		 * Special options
 		 */
 	case Opt_sloppy:
-		cfg->sloppy = 1;
+		sc->sloppy = 1;
 		dfprintk(MOUNT, "NFS:   relaxing parsing rules\n");
 		break;
 	case Opt_userspace:
@@ -845,116 +803,24 @@ static int nfs_sb_config_parse_option(struct nfs_sb_config *cfg, char *p)
 
 	default:
 		dfprintk(MOUNT, "NFS:   unrecognized mount option '%s'\n", p);
-		return -EINVAL;
+		if (!sc->sloppy)
+			return invalf("NFS: Unrecognized mount option '%s'", p);
+		break;
 	}
 
 	return 0;
 
-out_invalid_address:
-	printk(KERN_INFO "NFS: bad IP address specified: %s\n", p);
-	return -EINVAL;
-out_invalid_value:
-	printk(KERN_INFO "NFS: bad mount option value specified: %s\n", p);
-	return -EINVAL;
 out_nomem:
 	printk(KERN_INFO "NFS: not enough memory to parse option\n");
 	return -ENOMEM;
+out_invalid_value:
+	return invalf("NFS: Bad mount option value specified");
+out_invalid_address:
+	return invalf("NFS: Bad IP address specified");
 }
 
 /*
- * Error-check and convert a string of mount options from user space into
- * a data structure.  The whole mount string is processed; bad options are
- * skipped as they are encountered.  If there were no errors, return 1;
- * otherwise return 0 (zero).
- */
-int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
-{
-	char *p, *secdata;
-	int rc, sloppy = 0, invalid_option = 0;
-
-	if (!raw) {
-		dfprintk(MOUNT, "NFS: mount options string was NULL.\n");
-		return 1;
-	}
-	dfprintk(MOUNT, "NFS: nfs mount opts='%s'\n", raw);
-
-	secdata = alloc_secdata();
-	if (!secdata)
-		goto out_nomem;
-
-	rc = security_sb_copy_data(raw, secdata);
-	if (rc)
-		goto out_security_failure;
-
-	rc = security_sb_parse_opts_str(secdata, &cfg->lsm_opts);
-	if (rc)
-		goto out_security_failure;
-
-	free_secdata(secdata);
-
-	while ((p = strsep(&raw, ",")) != NULL) {
-		if (!*p)
-			continue;
-		if (nfs_sb_config_parse_option(cfg, p) < 0)
-			invalid_option = true;
-	}
-
-	if (!sloppy && invalid_option)
-		return 0;
-
-	if (cfg->minorversion && cfg->version != 4)
-		goto out_minorversion_mismatch;
-
-	if (cfg->options & NFS_OPTION_MIGRATION &&
-	    (cfg->version != 4 || cfg->minorversion != 0))
-		goto out_migration_misuse;
-
-	/*
-	 * verify that any proto=/mountproto= options match the address
-	 * families in the addr=/mountaddr= options.
-	 */
-	if (cfg->protofamily != AF_UNSPEC &&
-	    cfg->protofamily != cfg->nfs_server.address.sa_family)
-		goto out_proto_mismatch;
-
-	if (cfg->mountfamily != AF_UNSPEC) {
-		if (cfg->mount_server.addrlen) {
-			if (cfg->mountfamily != cfg->mount_server.address.sa_family)
-				goto out_mountproto_mismatch;
-		} else {
-			if (cfg->mountfamily != cfg->nfs_server.address.sa_family)
-				goto out_mountproto_mismatch;
-		}
-	}
-
-	return 1;
-
-out_minorversion_mismatch:
-	printk(KERN_INFO "NFS: mount option vers=%u does not support "
-			 "minorversion=%u\n", cfg->version, cfg->minorversion);
-	return 0;
-out_mountproto_mismatch:
-	printk(KERN_INFO "NFS: mount server address does not match mountproto= "
-			 "option\n");
-	return 0;
-out_proto_mismatch:
-	printk(KERN_INFO "NFS: server address does not match proto= option\n");
-	return 0;
-out_migration_misuse:
-	printk(KERN_INFO
-		"NFS: 'migration' not supported for this NFS version\n");
-	return -EINVAL;
-out_nomem:
-	printk(KERN_INFO "NFS: not enough memory to parse option\n");
-	return 0;
-out_security_failure:
-	free_secdata(secdata);
-	printk(KERN_INFO "NFS: security options invalid: %d\n", rc);
-	return 0;
-}
-
-/*
- * Split "dev_name" into "hostname:export_path".
+ * Split sc->device into "hostname:export_path".
  *
  * The leftmost colon demarks the split between the server's hostname
  * and the export path.  If the hostname starts with a left square
@@ -963,9 +829,9 @@ int nfs_parse_mount_options(char *raw, struct nfs_sb_config *cfg)
  * Note: caller frees hostname and export path, even on error.
  */
 static int nfs_parse_devname(struct nfs_sb_config *cfg,
-			     const char *dev_name,
 			     size_t maxnamlen, size_t maxpathlen)
 {
+	char *dev_name = cfg->sc.device;
 	size_t len;
 	char *end;
 
@@ -1009,19 +875,15 @@ static int nfs_parse_devname(struct nfs_sb_config *cfg,
 	return 0;
 
 out_bad_devname:
-	dfprintk(MOUNT, "NFS: device name not in host:path format\n");
-	return -EINVAL;
-
+	return invalf("NFS: device name not in host:path format");
 out_nomem:
-	dfprintk(MOUNT, "NFS: not enough memory to parse device name\n");
+	errorf("NFS: not enough memory to parse device name");
 	return -ENOMEM;
-
 out_hostname:
-	dfprintk(MOUNT, "NFS: server hostname too long\n");
+	errorf("NFS: server hostname too long");
 	return -ENAMETOOLONG;
-
 out_path:
-	dfprintk(MOUNT, "NFS: export pathname too long\n");
+	errorf("NFS: export pathname too long");
 	return -ENAMETOOLONG;
 }
 
@@ -1041,14 +903,14 @@ static int nfs_parse_devname(struct nfs_sb_config *cfg,
  * + breaking back: trying proto=udp after proto=tcp, v2 after v3,
  *   mountproto=tcp after mountproto=udp, and so on
  */
-static int nfs23_validate_mount_data(void *options,
-				     struct nfs_sb_config *cfg,
-				     struct nfs_fh *mntfh,
-				     const char *dev_name)
+static int nfs23_monolithic_mount_data(struct sb_config *sc,
+				       struct nfs_mount_data *data)
 {
-	struct nfs_mount_data *data = (struct nfs_mount_data *)options;
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+	struct nfs_fh *mntfh = cfg->mntfh;
 	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
 	int extra_flags = NFS_MOUNT_LEGACY_INTERFACE;
+	int ret;
 
 	if (data == NULL)
 		goto out_no_data;
@@ -1114,6 +976,9 @@ static int nfs23_validate_mount_data(void *options,
 			cfg->nfs_server.protocol = XPRT_TRANSPORT_UDP;
 		/* N.B. caller will free nfs_server.hostname in all cases */
 		cfg->nfs_server.hostname = kstrdup(data->hostname, GFP_KERNEL);
+		if (!cfg->nfs_server.hostname)
+			goto out_nomem;
+
 		cfg->namlen		= data->namlen;
 		cfg->bsize		= data->bsize;
 
@@ -1121,8 +986,6 @@ static int nfs23_validate_mount_data(void *options,
 			cfg->selected_flavor = data->pseudoflavor;
 		else
 			cfg->selected_flavor = RPC_AUTH_UNIX;
-		if (!cfg->nfs_server.hostname)
-			goto out_nomem;
 
 		if (!(data->flags & NFS_MOUNT_NONLM))
 			cfg->flags &= ~(NFS_MOUNT_LOCAL_FLOCK|
@@ -1130,6 +993,7 @@ static int nfs23_validate_mount_data(void *options,
 		else
 			cfg->flags |= (NFS_MOUNT_LOCAL_FLOCK|
 					NFS_MOUNT_LOCAL_FCNTL);
+
 		/*
 		 * The legacy version 6 binary mount data from userspace has a
 		 * field used only to transport selinux information into the
@@ -1140,17 +1004,16 @@ static int nfs23_validate_mount_data(void *options,
 		 */
 		if (data->context[0]){
 #ifdef CONFIG_SECURITY_SELINUX
-			int rc;
 			char *opts_str = kmalloc(sizeof(data->context) + 8, GFP_KERNEL);
 			if (!opts_str)
 				return -ENOMEM;
 			strcpy(opts_str, "context=");
 			data->context[NFS_MAX_CONTEXT_LEN] = '\0';
 			strcat(opts_str, &data->context[0]);
-			rc = security_sb_parse_opts_str(opts_str, &cfg->lsm_opts);
+			ret = vfs_parse_mount_option(sc, opts_str);
 			kfree(opts_str);
-			if (rc)
-				return rc;
+			if (ret)
+				return ret;
 #else
 			return -EINVAL;
 #endif
@@ -1158,54 +1021,44 @@ static int nfs23_validate_mount_data(void *options,
 
 		break;
 	default:
-		return NFS_TEXT_DATA;
+		return generic_monolithic_mount_data(sc, data);
 	}
 
+	cfg->skip_remount_option_check = true;
 	return 0;
 
 out_no_data:
-	dfprintk(MOUNT, "NFS: mount program didn't pass any mount data\n");
-	return -EINVAL;
+	if (sc->ms_flags & MS_REMOUNT) {
+		cfg->skip_remount_option_check = true;
+		return 0;
+	}
+	return invalf("NFS: mount program didn't pass any mount data");
 
 out_no_v3:
-	dfprintk(MOUNT, "NFS: nfs_mount_data version %d does not support v3\n",
-		 data->version);
-	return -EINVAL;
+	return invalf("NFS: nfs_mount_data version does not support v3");
 
 out_no_sec:
-	dfprintk(MOUNT, "NFS: nfs_mount_data version supports only AUTH_SYS\n");
-	return -EINVAL;
+	return invalf("NFS: nfs_mount_data version supports only AUTH_SYS");
 
 out_nomem:
-	dfprintk(MOUNT, "NFS: not enough memory to handle mount options\n");
+	dfprintk(MOUNT, "NFS: not enough memory to handle mount options");
 	return -ENOMEM;
 
 out_no_address:
-	dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
-	return -EINVAL;
+	return invalf("NFS: mount program didn't pass remote address");
 
 out_invalid_fh:
-	dfprintk(MOUNT, "NFS: invalid root filehandle\n");
-	return -EINVAL;
-}
-
-#if IS_ENABLED(CONFIG_NFS_V4)
-
-static void nfs4_validate_mount_flags(struct nfs_sb_config *cfg)
-{
-	cfg->flags &= ~(NFS_MOUNT_NONLM|NFS_MOUNT_NOACL|NFS_MOUNT_VER3|
-			 NFS_MOUNT_LOCAL_FLOCK|NFS_MOUNT_LOCAL_FCNTL);
+	return invalf("NFS: invalid root filehandle");
 }
 
 /*
  * Validate NFSv4 mount options
  */
-static int nfs4_validate_mount_data(void *options,
-				    struct nfs_sb_config *cfg,
-				    const char *dev_name)
+static int nfs4_monolithic_mount_data(struct sb_config *sc,
+				      struct nfs4_mount_data *data)
 {
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
 	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
-	struct nfs4_mount_data *data = (struct nfs4_mount_data *)options;
 	char *c;
 
 	if (data == NULL)
@@ -1255,7 +1108,7 @@ static int nfs4_validate_mount_data(void *options,
 		cfg->client_address = c;
 
 		/*
-		 * Translate to nfs_sb_config, which nfs4_fill_super
+		 * Translate to nfs_sb_config, which nfs_fill_super
 		 * can deal with.
 		 */
 
@@ -1275,95 +1128,372 @@ static int nfs4_validate_mount_data(void *options,
 
 		break;
 	default:
-		return NFS_TEXT_DATA;
+		return generic_monolithic_mount_data(sc, data);
 	}
 
+	cfg->skip_remount_option_check = true;
 	return 0;
 
 out_no_data:
-	dfprintk(MOUNT, "NFS4: mount program didn't pass any mount data\n");
-	return -EINVAL;
+	if (sc->ms_flags & MS_REMOUNT) {
+		cfg->skip_remount_option_check = true;
+		return 0;
+	}
+	return invalf("NFS4: mount program didn't pass any mount data");
 
 out_inval_auth:
-	dfprintk(MOUNT, "NFS4: Invalid number of RPC auth flavours %d\n",
-		 data->auth_flavourlen);
-	return -EINVAL;
+	return invalf("NFS4: Invalid number of RPC auth flavours %d",
+		      data->auth_flavourlen);
 
 out_no_address:
-	dfprintk(MOUNT, "NFS4: mount program didn't pass remote address\n");
-	return -EINVAL;
+	return invalf("NFS4: mount program didn't pass remote address");
 
 out_invalid_transport_udp:
-	dfprintk(MOUNT, "NFSv4: Unsupported transport protocol udp\n");
-	return -EINVAL;
+	return invalf("NFSv4: Unsupported transport protocol udp");
 }
 
-int nfs_validate_mount_data(struct file_system_type *fs_type,
-			    void *options,
-			    struct nfs_sb_config *cfg,
-			    struct nfs_fh *mntfh,
-			    const char *dev_name)
-{
-	if (fs_type == &nfs_fs_type)
-		return nfs23_validate_mount_data(options, cfg, mntfh, dev_name);
-	return nfs4_validate_mount_data(options, cfg, dev_name);
-}
-#else
-int nfs_validate_mount_data(struct file_system_type *fs_type,
-			    void *options,
-			    struct nfs_sb_config *cfg,
-			    struct nfs_fh *mntfh,
-			    const char *dev_name)
+/*
+ * Parse a monolithic block of data from sys_mount().
+ */
+static int nfs_monolithic_mount_data(struct sb_config *sc, void *data)
 {
-	return nfs23_validate_mount_data(options, cfg, mntfh, dev_name);
-}
+	if (sc->fs_type == &nfs_fs_type)
+		return nfs23_monolithic_mount_data(sc, data);
+
+#if IS_ENABLED(CONFIG_NFS_V4)
+	if (sc->fs_type == &nfs4_fs_type)
+		return nfs4_monolithic_mount_data(sc, data);
 #endif
 
-int nfs_validate_text_mount_data(void *options,
-				 struct nfs_sb_config *cfg,
-				 const char *dev_name)
+	return invalf("NFS: Unsupported monolithic data version");
+}
+
+/*
+ * Validate the preparsed information in the config.
+ */
+static int nfs_sb_config_validate(struct sb_config *sc)
 {
-	int port = 0;
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+	struct nfs_subversion *nfs_mod;
+	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
 	int max_namelen = PAGE_SIZE;
 	int max_pathlen = NFS_MAXPATHLEN;
-	struct sockaddr *sap = (struct sockaddr *)&cfg->nfs_server.address;
+	int port = 0;
+	int ret;
 
-	if (nfs_parse_mount_options((char *)options, cfg) == 0)
-		return -EINVAL;
+	if (cfg->sc.purpose == SB_CONFIG_FOR_REMOUNT)
+		return 0;
+
+	if (!cfg->sc.device)
+		goto out_no_device_name;
+
+	/* Check for sanity first. */
+	if (cfg->minorversion && cfg->version != 4)
+		goto out_minorversion_mismatch;
+
+	if (cfg->options & NFS_OPTION_MIGRATION &&
+	    (cfg->version != 4 || cfg->minorversion != 0))
+		goto out_migration_misuse;
+
+	/* Verify that any proto=/mountproto= options match the address
+	 * families in the addr=/mountaddr= options.
+	 */
+	if (cfg->protofamily != AF_UNSPEC &&
+	    cfg->protofamily != cfg->nfs_server.address.sa_family)
+		goto out_proto_mismatch;
+
+	if (cfg->mountfamily != AF_UNSPEC) {
+		if (cfg->mount_server.addrlen) {
+			if (cfg->mountfamily != cfg->mount_server.address.sa_family)
+				goto out_mountproto_mismatch;
+		} else {
+			if (cfg->mountfamily != cfg->nfs_server.address.sa_family)
+				goto out_mountproto_mismatch;
+		}
+	}
 
 	if (!nfs_verify_server_address(sap))
 		goto out_no_address;
 
 	if (cfg->version == 4) {
-#if IS_ENABLED(CONFIG_NFS_V4)
-		port = NFS_PORT;
-		max_namelen = NFS4_MAXNAMLEN;
-		max_pathlen = NFS4_MAXPATHLEN;
-		nfs_validate_transport_protocol(cfg);
-		if (cfg->nfs_server.protocol == XPRT_TRANSPORT_UDP)
-			goto out_invalid_transport_udp;
-		nfs4_validate_mount_flags(cfg);
-#else
-		goto out_v4_not_compiled;
-#endif /* CONFIG_NFS_V4 */
-	} else
+		if (IS_ENABLED(CONFIG_NFS_V4)) {
+			port = NFS_PORT;
+			max_namelen = NFS4_MAXNAMLEN;
+			max_pathlen = NFS4_MAXPATHLEN;
+			nfs_validate_transport_protocol(cfg);
+			if (cfg->nfs_server.protocol == XPRT_TRANSPORT_UDP)
+				goto out_invalid_transport_udp;
+			cfg->flags &= ~(NFS_MOUNT_NONLM | NFS_MOUNT_NOACL |
+					 NFS_MOUNT_VER3 | NFS_MOUNT_LOCAL_FLOCK |
+					 NFS_MOUNT_LOCAL_FCNTL);
+		} else {
+			goto out_v4_not_compiled;
+		}
+	} else {
 		nfs_set_mount_transport_protocol(cfg);
+	}
 
 	nfs_set_port(sap, &cfg->nfs_server.port, port);
 
-	return nfs_parse_devname(cfg, dev_name, max_namelen, max_pathlen);
+	ret = nfs_parse_devname(cfg, max_namelen, max_pathlen);
+	if (ret < 0)
+		return ret;
 
-#if !IS_ENABLED(CONFIG_NFS_V4)
+	/* Load the NFS protocol module if we haven't done so yet */
+	if (!cfg->nfs_mod) {
+		nfs_mod = get_nfs_version(cfg->version);
+		if (IS_ERR(nfs_mod)) {
+			ret = PTR_ERR(nfs_mod);
+			goto out_version_unavailable;
+		}
+		cfg->nfs_mod = nfs_mod;
+	}
+	return 0;
+
+out_no_device_name:
+	return invalf("NFS: Device name not specified");
 out_v4_not_compiled:
-	dfprintk(MOUNT, "NFS: NFSv4 is not compiled into kernel\n");
+	errorf("NFS: NFSv4 is not compiled into kernel");
 	return -EPROTONOSUPPORT;
-#else
 out_invalid_transport_udp:
-	dfprintk(MOUNT, "NFSv4: Unsupported transport protocol udp\n");
-	return -EINVAL;
-#endif /* !CONFIG_NFS_V4 */
-
+	return invalf("NFSv4: Unsupported transport protocol udp");
 out_no_address:
-	dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
-	return -EINVAL;
+	return invalf("NFS: mount program didn't pass remote address");
+out_mountproto_mismatch:
+	return invalf("NFS: Mount server address does not match mountproto= option");
+out_proto_mismatch:
+	return invalf("NFS: Server address does not match proto= option");
+out_minorversion_mismatch:
+	return invalf("NFS: Mount option vers=%u does not support minorversion=%u",
+		      cfg->version, cfg->minorversion);
+out_migration_misuse:
+	return invalf("NFS: 'Migration' not supported for this NFS version");
+out_version_unavailable:
+	errorf("NFS: Version unavailable");
+	return ret;
+}
+
+/*
+ * Use the preparsed information in the config to effect a mount.
+ */
+static int nfs_get_ordinary_tree(struct nfs_sb_config *cfg)
+{
+	cfg->set_security = nfs_set_sb_security;
+
+	return cfg->nfs_mod->rpc_ops->try_get_tree(cfg);
 }
+
+/*
+ * Clone an NFS2/3/4 server record on xdev traversal (FSID-change)
+ */
+static int nfs_get_xdev_tree(struct nfs_sb_config *cfg)
+{
+	struct nfs_server *server;
+	int ret;
+
+	dprintk("--> nfs_xdev_mount()\n");
+
+	cfg->set_security = nfs_clone_sb_security;
+
+	/* create a new volume representation */
+	server = cfg->nfs_mod->rpc_ops->clone_server(NFS_SB(cfg->clone_data.sb),
+						     cfg->mntfh,
+						     cfg->clone_data.fattr,
+						     cfg->selected_flavor);
+
+	if (IS_ERR(server))
+		ret = PTR_ERR(server);
+	else
+		ret = nfs_get_tree_common(server, cfg);
+
+	dprintk("<-- nfs_get_xdev_tree() = %d\n", ret);
+	return ret;
+}
+
+/*
+ * Create an NFS superblock by the appropriate method.
+ */
+static int nfs_get_tree(struct sb_config *sc)
+{
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+	int ret;
+
+	if (!cfg->nfs_mod) {
+		pr_warn("Missing nfs_mod\n");
+		return -EINVAL;
+	}
+	if (!cfg->nfs_mod->rpc_ops) {
+		pr_warn("Missing rpc_ops\n");
+		return -EINVAL;
+	}
+
+	if (cfg->nfs_mod->rpc_ops->get_tree) {
+		ret = cfg->nfs_mod->rpc_ops->get_tree(cfg);
+		if (ret != 1)
+			return ret;
+	}
+
+	switch (cfg->mount_type) {
+	case NFS_MOUNT_ORDINARY:
+		return nfs_get_ordinary_tree(cfg);
+
+	case NFS_MOUNT_CROSS_DEV:
+		return nfs_get_xdev_tree(cfg);
+
+	default:
+		errorf("NFS: Unknown mount type");
+		return -ENOTSUPP;
+	}
+}
+
+/*
+ * Handle duplication of a configuration.  The caller copied *src into *sc, but
+ * it can't deal with resource pointers in the filesystem context, so we have
+ * to do that.  We need to clear pointers, copy data or get extra refs as
+ * appropriate.
+ */
+static int nfs_sb_config_dup(struct sb_config *sc, struct sb_config *src)
+{
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+
+	__module_get(cfg->nfs_mod->owner);
+	cfg->client_address		= NULL;
+	cfg->mount_server.hostname	= NULL;
+	cfg->nfs_server.export_path	= NULL;
+	cfg->nfs_server.hostname	= NULL;
+	cfg->fscache_uniq		= NULL;
+
+	cfg->mntfh = nfs_alloc_fhandle();
+	if (!cfg->mntfh)
+		return -ENOMEM;
+	return 0;
+}
+
+static void nfs_sb_config_free(struct sb_config *sc)
+{
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+
+	if (cfg->nfs_mod)
+		put_nfs_version(cfg->nfs_mod);
+	kfree(cfg->client_address);
+	kfree(cfg->mount_server.hostname);
+	if (cfg->nfs_server.export_path != nfs_slash)
+		kfree(cfg->nfs_server.export_path);
+	kfree(cfg->nfs_server.hostname);
+	kfree(cfg->fscache_uniq);
+	nfs_free_fhandle(cfg->mntfh);
+}
+
+static const struct sb_config_operations nfs_sb_config_ops = {
+	.free			= nfs_sb_config_free,
+	.dup			= nfs_sb_config_dup,
+	.parse_option		= nfs_sb_config_parse_option,
+	.monolithic_mount_data	= nfs_monolithic_mount_data,
+	.validate		= nfs_sb_config_validate,
+	.get_tree		= nfs_get_tree,
+};
+
+/*
+ * Initialise a configuration from an extant superblock for remounting.
+ */
+static int nfs_mount_init_from_sb(struct sb_config *sc,
+				  struct super_block *sb)
+{
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+	struct nfs_server *nfss = sb->s_fs_info;
+	struct net *net = nfss->nfs_client->cl_net;
+
+	cfg->flags		= nfss->flags;
+	cfg->rsize		= nfss->rsize;
+	cfg->wsize		= nfss->wsize;
+	cfg->retrans		= nfss->client->cl_timeout->to_retries;
+	cfg->selected_flavor	= nfss->client->cl_auth->au_flavor;
+	cfg->acregmin		= nfss->acregmin / HZ;
+	cfg->acregmax		= nfss->acregmax / HZ;
+	cfg->acdirmin		= nfss->acdirmin / HZ;
+	cfg->acdirmax		= nfss->acdirmax / HZ;
+	cfg->timeo		= 10U * nfss->client->cl_timeout->to_initval / HZ;
+	cfg->nfs_server.port	= nfss->port;
+	cfg->nfs_server.addrlen	= nfss->nfs_client->cl_addrlen;
+	cfg->version		= nfss->nfs_client->rpc_ops->version;
+	cfg->minorversion	= nfss->nfs_client->cl_minorversion;
+
+	memcpy(&cfg->nfs_server.address, &nfss->nfs_client->cl_addr,
+		cfg->nfs_server.addrlen);
+
+	if (cfg->sc.net_ns != net) {
+		put_net(cfg->sc.net_ns);
+		cfg->sc.net_ns = get_net(net);
+	}
+
+	cfg->nfs_mod = nfss->nfs_client->cl_nfs_mod;
+	if (!try_module_get(cfg->nfs_mod->owner)) {
+		cfg->nfs_mod = NULL;
+		errorf("NFS: Protocol module not available");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/*
+ * Prepare superblock configuration.  We use the namespaces attached to the
+ * context.  This may be the current process's namespaces, or it may be a
+ * container's namespaces.
+ */
+static int nfs_init_sb_config(struct sb_config *sc, struct super_block *src_sb)
+{
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+
+	cfg->mntfh = nfs_alloc_fhandle();
+	if (!cfg->mntfh)
+		return -ENOMEM;
+
+	cfg->sc.ops		= &nfs_sb_config_ops;
+	cfg->mount_type		= NFS_MOUNT_ORDINARY;
+	cfg->protofamily	= AF_UNSPEC;
+	cfg->mountfamily	= AF_UNSPEC;
+	cfg->mount_server.port	= NFS_UNSPEC_PORT;
+
+	if (!src_sb) {
+		cfg->timeo		= NFS_UNSPEC_TIMEO;
+		cfg->retrans		= NFS_UNSPEC_RETRANS;
+		cfg->acregmin		= NFS_DEF_ACREGMIN;
+		cfg->acregmax		= NFS_DEF_ACREGMAX;
+		cfg->acdirmin		= NFS_DEF_ACDIRMIN;
+		cfg->acdirmax		= NFS_DEF_ACDIRMAX;
+		cfg->nfs_server.port	= NFS_UNSPEC_PORT;
+		cfg->nfs_server.protocol = XPRT_TRANSPORT_TCP;
+		cfg->selected_flavor	= RPC_AUTH_MAXFLAVOR;
+		cfg->minorversion	= 0;
+		cfg->need_mount		= true;
+		return 0;
+	}
+
+	return nfs_mount_init_from_sb(sc, src_sb);
+}
+
+struct file_system_type nfs_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "nfs",
+	.init_sb_config	= nfs_init_sb_config,
+	.sb_config_size	= sizeof(struct nfs_sb_config),
+	.kill_sb	= nfs_kill_super,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+};
+MODULE_ALIAS_FS("nfs");
+EXPORT_SYMBOL_GPL(nfs_fs_type);
+
+#if IS_ENABLED(CONFIG_NFS_V4)
+struct file_system_type nfs4_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "nfs4",
+	.sb_config_size	= sizeof(struct nfs_sb_config),
+	.init_sb_config	= nfs_init_sb_config,
+	.kill_sb	= nfs_kill_super,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+};
+MODULE_ALIAS_FS("nfs4");
+MODULE_ALIAS("nfs4");
+EXPORT_SYMBOL_GPL(nfs4_fs_type);
+#endif /* CONFIG_NFS_V4 */
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index 1a224a33a6c2..e95e669e4db8 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -18,6 +18,7 @@
 #include <linux/vfs.h>
 #include <linux/sunrpc/gss_api.h>
 #include "internal.h"
+#include "nfs.h"
 
 #define NFSDBG_FACILITY		NFSDBG_VFS
 
@@ -213,10 +214,9 @@ void nfs_release_automount_timer(void)
  * Clone a mountpoint of the appropriate type
  */
 static struct vfsmount *nfs_do_clone_mount(struct nfs_server *server,
-					   const char *devname,
-					   struct nfs_clone_mount *mountdata)
+					   struct nfs_sb_config *cfg)
 {
-	return vfs_submount(mountdata->dentry, &nfs_xdev_fs_type, devname, mountdata);
+	return vfs_submount_sc(cfg->clone_data.dentry, &cfg->sc);
 }
 
 /**
@@ -230,27 +230,58 @@ static struct vfsmount *nfs_do_clone_mount(struct nfs_server *server,
 struct vfsmount *nfs_do_submount(struct dentry *dentry, struct nfs_fh *fh,
 				 struct nfs_fattr *fattr, rpc_authflavor_t authflavor)
 {
-	struct nfs_clone_mount mountdata = {
-		.sb = dentry->d_sb,
-		.dentry = dentry,
-		.fh = fh,
-		.fattr = fattr,
-		.authflavor = authflavor,
-	};
+	struct nfs_sb_config *cfg;
+	struct sb_config *sc;
 	struct vfsmount *mnt;
-	char *page = (char *) __get_free_page(GFP_USER);
-	char *devname;
+	char *buffer, *p;
+	int ret;
 
-	if (page == NULL)
-		return ERR_PTR(-ENOMEM);
+	/* Open a new mount context, transferring parameters from the parent
+	 * superblock, including the network namespace.
+	 */
+	sc = __vfs_new_sb_config(&nfs_fs_type, dentry->d_sb, 0,
+				 SB_CONFIG_FOR_SUBMOUNT);
+	if (IS_ERR(sc))
+		return ERR_CAST(sc);
+	cfg = container_of(sc, struct nfs_sb_config, sc);
 
-	devname = nfs_devname(dentry, page, PAGE_SIZE);
-	if (IS_ERR(devname))
-		mnt = (struct vfsmount *)devname;
-	else
-		mnt = nfs_do_clone_mount(NFS_SB(dentry->d_sb), devname, &mountdata);
+	mnt = ERR_PTR(-ENOMEM);
+	buffer = kmalloc(4096, GFP_USER);
+	if (!buffer)
+		goto err_sc;
+
+	cfg->mount_type		= NFS_MOUNT_CROSS_DEV;
+	cfg->selected_flavor	= authflavor;
+	cfg->clone_data.sb	= dentry->d_sb;
+	cfg->clone_data.dentry	= dentry;
+	cfg->clone_data.fattr	= fattr;
+	cfg->clone_data.cloned	= true;
+
+	nfs_copy_fh(cfg->mntfh, fh);
+
+	p = nfs_devname(dentry, buffer, 4096);
+	if (IS_ERR(p)) {
+		errorf("NFS: Couldn't determine submount pathname");
+		mnt = ERR_CAST(p);
+		goto err_buffer;
+	}
+
+	cfg->sc.device = kmemdup(p, buffer + 4096 - p, GFP_KERNEL);
+	kfree(buffer);
+	if (!cfg->sc.device)
+		goto err_sc;
+
+	ret = vfs_get_tree(sc);
+	if (ret < 0)
+		goto err_sc;
+
+	mnt = nfs_do_clone_mount(NFS_SB(dentry->d_sb), cfg);
+	goto err_sc;
 
-	free_page((unsigned long)page);
+err_buffer:
+	kfree(buffer);
+err_sc:
+	put_sb_config(sc);
 	return mnt;
 }
 EXPORT_SYMBOL_GPL(nfs_do_submount);
diff --git a/fs/nfs/nfs3_fs.h b/fs/nfs/nfs3_fs.h
index e134d6548ab7..7187e2e28a65 100644
--- a/fs/nfs/nfs3_fs.h
+++ b/fs/nfs/nfs3_fs.h
@@ -26,7 +26,7 @@ static inline int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl,
 #endif /* CONFIG_NFS_V3_ACL */
 
 /* nfs3client.c */
-struct nfs_server *nfs3_create_server(struct nfs_mount_info *, struct nfs_subversion *);
+struct nfs_server *nfs3_create_server(struct nfs_sb_config *);
 struct nfs_server *nfs3_clone_server(struct nfs_server *, struct nfs_fh *,
 				     struct nfs_fattr *, rpc_authflavor_t);
 
diff --git a/fs/nfs/nfs3client.c b/fs/nfs/nfs3client.c
index 7879f2a0fcfd..8b688d71c37c 100644
--- a/fs/nfs/nfs3client.c
+++ b/fs/nfs/nfs3client.c
@@ -45,10 +45,10 @@ static inline void nfs_init_server_aclclient(struct nfs_server *server)
 }
 #endif
 
-struct nfs_server *nfs3_create_server(struct nfs_mount_info *mount_info,
-				      struct nfs_subversion *nfs_mod)
+struct nfs_server *nfs3_create_server(struct nfs_sb_config *cfg)
 {
-	struct nfs_server *server = nfs_create_server(mount_info, nfs_mod);
+	struct nfs_server *server = nfs_create_server(cfg);
+
 	/* Create a client RPC handle for the NFS v3 ACL management interface */
 	if (!IS_ERR(server))
 		nfs_init_server_aclclient(server);
diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
index 0c07b567118d..96e315916a34 100644
--- a/fs/nfs/nfs3proc.c
+++ b/fs/nfs/nfs3proc.c
@@ -975,7 +975,7 @@ const struct nfs_rpc_ops nfs_v3_clientops = {
 	.nlmclnt_ops	= &nlmclnt_fl_close_lock_ops,
 	.getroot	= nfs3_proc_get_root,
 	.submount	= nfs_submount,
-	.try_mount	= nfs_try_mount,
+	.try_get_tree	= nfs_try_get_tree,
 	.getattr	= nfs3_proc_getattr,
 	.setattr	= nfs3_proc_setattr,
 	.lookup		= nfs3_proc_lookup,
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index af285cc27ccf..effcea28d7cc 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -467,7 +467,6 @@ extern const nfs4_stateid zero_stateid;
 /* nfs4super.c */
 struct nfs_mount_info;
 extern struct nfs_subversion nfs_v4;
-struct dentry *nfs4_try_mount(int, const char *, struct nfs_mount_info *, struct nfs_subversion *);
 extern bool nfs4_disable_idmapping;
 extern unsigned short max_session_slots;
 extern unsigned short max_session_cb_slots;
@@ -477,6 +476,9 @@ extern bool recover_lost_locks;
 #define NFS4_CLIENT_ID_UNIQ_LEN		(64)
 extern char nfs4_client_id_uniquifier[NFS4_CLIENT_ID_UNIQ_LEN];
 
+extern int nfs4_try_get_tree(struct nfs_sb_config *);
+extern int nfs4_get_tree(struct nfs_sb_config *);
+
 /* nfs4sysctl.c */
 #ifdef CONFIG_SYSCTL
 int nfs4_register_sysctl(void);
diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
index ec0c112d8148..f1b9b9c7db12 100644
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -1033,14 +1033,14 @@ static int nfs4_init_server(struct nfs_server *server,
 
 	/* Get a client record */
 	error = nfs4_set_client(server,
-			cfg->nfs_server.hostname,
-			(const struct sockaddr *)&cfg->nfs_server.address,
-			cfg->nfs_server.addrlen,
-			cfg->client_address,
-			cfg->nfs_server.protocol,
-			&timeparms,
-			cfg->minorversion,
-			cfg->net);
+				cfg->nfs_server.hostname,
+				&cfg->nfs_server.address,
+				cfg->nfs_server.addrlen,
+				cfg->client_address,
+				cfg->nfs_server.protocol,
+				&timeparms,
+				cfg->minorversion,
+				cfg->sc.net_ns);
 	if (error < 0)
 		return error;
 
@@ -1063,10 +1063,7 @@ static int nfs4_init_server(struct nfs_server *server,
  * Create a version 4 volume record
  * - keyed on server and FSID
  */
-/*struct nfs_server *nfs4_create_server(const struct nfs_sb_config *data,
-				      struct nfs_fh *mntfh)*/
-struct nfs_server *nfs4_create_server(struct nfs_mount_info *mount_info,
-				      struct nfs_subversion *nfs_mod)
+struct nfs_server *nfs4_create_server(struct nfs_sb_config *cfg)
 {
 	struct nfs_server *server;
 	bool auth_probe;
@@ -1076,14 +1073,14 @@ struct nfs_server *nfs4_create_server(struct nfs_mount_info *mount_info,
 	if (!server)
 		return ERR_PTR(-ENOMEM);
 
-	auth_probe = mount_info->cfg->auth_info.flavor_len < 1;
+	auth_probe = cfg->auth_info.flavor_len < 1;
 
 	/* set up the general RPC client */
-	error = nfs4_init_server(server, mount_info->cfg);
+	error = nfs4_init_server(server, cfg);
 	if (error < 0)
 		goto error;
 
-	error = nfs4_server_common_setup(server, mount_info->mntfh, auth_probe);
+	error = nfs4_server_common_setup(server, cfg->mntfh, auth_probe);
 	if (error < 0)
 		goto error;
 
@@ -1097,8 +1094,7 @@ struct nfs_server *nfs4_create_server(struct nfs_mount_info *mount_info,
 /*
  * Create an NFS4 referral server record
  */
-struct nfs_server *nfs4_create_referral_server(struct nfs_clone_mount *data,
-					       struct nfs_fh *mntfh)
+struct nfs_server *nfs4_create_referral_server(struct nfs_sb_config *cfg)
 {
 	struct nfs_client *parent_client;
 	struct nfs_server *server, *parent_server;
@@ -1109,7 +1105,7 @@ struct nfs_server *nfs4_create_referral_server(struct nfs_clone_mount *data,
 	if (!server)
 		return ERR_PTR(-ENOMEM);
 
-	parent_server = NFS_SB(data->sb);
+	parent_server = NFS_SB(cfg->clone_data.sb);
 	parent_client = parent_server->nfs_client;
 
 	/* Initialise the client representation from the parent server */
@@ -1117,9 +1113,10 @@ struct nfs_server *nfs4_create_referral_server(struct nfs_clone_mount *data,
 
 	/* Get a client representation.
 	 * Note: NFSv4 always uses TCP, */
-	error = nfs4_set_client(server, data->hostname,
-				data->addr,
-				data->addrlen,
+	error = nfs4_set_client(server,
+				cfg->nfs_server.hostname,
+				&cfg->nfs_server.address,
+				cfg->nfs_server.addrlen,
 				parent_client->cl_ipaddr,
 				rpc_protocol(parent_server->client),
 				parent_server->client->cl_timeout,
@@ -1128,13 +1125,14 @@ struct nfs_server *nfs4_create_referral_server(struct nfs_clone_mount *data,
 	if (error < 0)
 		goto error;
 
-	error = nfs_init_server_rpcclient(server, parent_server->client->cl_timeout, data->authflavor);
+	error = nfs_init_server_rpcclient(server, parent_server->client->cl_timeout,
+					  cfg->selected_flavor);
 	if (error < 0)
 		goto error;
 
 	auth_probe = parent_server->auth_info.flavor_len < 1;
 
-	error = nfs4_server_common_setup(server, mntfh, auth_probe);
+	error = nfs4_server_common_setup(server, cfg->mntfh, auth_probe);
 	if (error < 0)
 		goto error;
 
diff --git a/fs/nfs/nfs4namespace.c b/fs/nfs/nfs4namespace.c
index 7d531da1bae3..60b711aa0618 100644
--- a/fs/nfs/nfs4namespace.c
+++ b/fs/nfs/nfs4namespace.c
@@ -7,6 +7,7 @@
  * NFSv4 namespace
  */
 
+#include <linux/module.h>
 #include <linux/dcache.h>
 #include <linux/mount.h>
 #include <linux/namei.h>
@@ -20,37 +21,64 @@
 #include <linux/inet.h>
 #include "internal.h"
 #include "nfs4_fs.h"
+#include "nfs.h"
 #include "dns_resolve.h"
 
 #define NFSDBG_FACILITY		NFSDBG_VFS
 
 /*
+ * Work out the length that an NFSv4 path would render to as a standard posix
+ * path, with a leading slash but no terminating slash.
+ */
+static ssize_t nfs4_pathname_len(const struct nfs4_pathname *pathname)
+{
+	ssize_t len;
+	int i;
+
+	for (i = 0; i < pathname->ncomponents; i++) {
+		const struct nfs4_string *component = &pathname->components[i];
+
+		if (component->len > NAME_MAX)
+			goto too_long;
+		len += 1 + component->len; /* Adding "/foo" */
+		if (len > PATH_MAX)
+			goto too_long;
+	}
+	return len;
+
+too_long:
+	return -ENAMETOOLONG;
+}
+
+/*
  * Convert the NFSv4 pathname components into a standard posix path.
- *
- * Note that the resulting string will be placed at the end of the buffer
  */
-static inline char *nfs4_pathname_string(const struct nfs4_pathname *pathname,
-					 char *buffer, ssize_t buflen)
+static char *nfs4_pathname_string(const struct nfs4_pathname *pathname,
+				  unsigned short *_len)
 {
-	char *end = buffer + buflen;
-	int n;
+	ssize_t len;
+	char *buf, *p;
+	int i;
 
-	*--end = '\0';
-	buflen--;
-
-	n = pathname->ncomponents;
-	while (--n >= 0) {
-		const struct nfs4_string *component = &pathname->components[n];
-		buflen -= component->len + 1;
-		if (buflen < 0)
-			goto Elong;
-		end -= component->len;
-		memcpy(end, component->data, component->len);
-		*--end = '/';
+	len = nfs4_pathname_len(pathname);
+	if (len < 0)
+		return ERR_PTR(len);
+	*_len = len;
+
+	p = buf = kmalloc(len + 1, GFP_KERNEL);
+	if (!buf)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = 0; i < pathname->ncomponents; i++) {
+		const struct nfs4_string *component = &pathname->components[i];
+
+		*p++ = '/';
+		memcpy(p, component->data, component->len);
+		p += component->len;
 	}
-	return end;
-Elong:
-	return ERR_PTR(-ENAMETOOLONG);
+
+	*p = 0;
+	return buf;
 }
 
 /*
@@ -99,21 +127,25 @@ static char *nfs4_path(struct dentry *dentry, char *buffer, ssize_t buflen)
  */
 static int nfs4_validate_fspath(struct dentry *dentry,
 				const struct nfs4_fs_locations *locations,
-				char *page, char *page2)
+				struct nfs_sb_config *cfg)
 {
-	const char *path, *fs_path;
+	const char *path;
+	char *buf;
+	int n;
 
-	path = nfs4_path(dentry, page, PAGE_SIZE);
-	if (IS_ERR(path))
+	buf = kmalloc(4096, GFP_KERNEL);
+	path = nfs4_path(dentry, buf, 4096);
+	if (IS_ERR(path)) {
+		kfree(buf);
 		return PTR_ERR(path);
+	}
 
-	fs_path = nfs4_pathname_string(&locations->fs_path, page2, PAGE_SIZE);
-	if (IS_ERR(fs_path))
-		return PTR_ERR(fs_path);
-
-	if (strncmp(path, fs_path, strlen(fs_path)) != 0) {
+	n = strncmp(path, cfg->nfs_server.export_path,
+		    cfg->nfs_server.export_path_len);
+	kfree(buf);
+	if (n != 0) {
 		dprintk("%s: path %s does not begin with fsroot %s\n",
-			__func__, path, fs_path);
+			__func__, path, cfg->nfs_server.export_path);
 		return -ENOENT;
 	}
 
@@ -234,56 +266,66 @@ nfs4_negotiate_security(struct rpc_clnt *clnt, struct inode *inode,
 	return new;
 }
 
-static struct vfsmount *try_location(struct nfs_clone_mount *mountdata,
-				     char *page, char *page2,
+static struct vfsmount *try_location(struct dentry *dentry,
+				     struct nfs_sb_config *cfg,
 				     const struct nfs4_fs_location *location)
 {
-	const size_t addr_bufsize = sizeof(struct sockaddr_storage);
-	struct net *net = rpc_net_ns(NFS_SB(mountdata->sb)->client);
+	struct net *net = rpc_net_ns(NFS_SB(dentry->d_sb)->client);
 	struct vfsmount *mnt = ERR_PTR(-ENOENT);
-	char *mnt_path;
-	unsigned int maxbuflen;
-	unsigned int s;
+	unsigned int len, s;
+	char *p;
 
-	mnt_path = nfs4_pathname_string(&location->rootpath, page2, PAGE_SIZE);
-	if (IS_ERR(mnt_path))
-		return ERR_CAST(mnt_path);
-	mountdata->mnt_path = mnt_path;
-	maxbuflen = mnt_path - 1 - page2;
+	/* Allocate a buffer big enough to hold any of the hostnames plus a
+	 * terminating char and also a buffer big enough to hold the hostname
+	 * plus a colon plus the path.
+	 */
+	len = 0;
+	for (s = 0; s < location->nservers; s++) {
+		const struct nfs4_string *buf = &location->servers[s];
+		if (buf->len > len)
+			len = buf->len;
+	}
 
-	mountdata->addr = kmalloc(addr_bufsize, GFP_KERNEL);
-	if (mountdata->addr == NULL)
+	cfg->nfs_server.hostname = kmalloc(len + 1, GFP_KERNEL);
+	if (!cfg->nfs_server.hostname)
 		return ERR_PTR(-ENOMEM);
 
+	cfg->sc.device = kmalloc(len + 1 + cfg->nfs_server.export_path_len + 1,
+				 GFP_KERNEL);
+	if (!cfg->sc.device)
+		return ERR_PTR(-ENOMEM);
+	
 	for (s = 0; s < location->nservers; s++) {
 		const struct nfs4_string *buf = &location->servers[s];
 
-		if (buf->len <= 0 || buf->len >= maxbuflen)
-			continue;
-
 		if (memchr(buf->data, IPV6_SCOPE_DELIMITER, buf->len))
 			continue;
 
-		mountdata->addrlen = nfs_parse_server_name(buf->data, buf->len,
-				mountdata->addr, addr_bufsize, net);
-		if (mountdata->addrlen == 0)
+		cfg->nfs_server.addrlen =
+			nfs_parse_server_name(buf->data, buf->len,
+					      &cfg->nfs_server.address,
+					      sizeof(cfg->nfs_server._address),
+					      net);
+		if (cfg->nfs_server.addrlen == 0)
 			continue;
 
-		rpc_set_port(mountdata->addr, NFS_PORT);
+		rpc_set_port(&cfg->nfs_server.address, NFS_PORT);
 
-		memcpy(page2, buf->data, buf->len);
-		page2[buf->len] = '\0';
-		mountdata->hostname = page2;
+		memcpy(cfg->nfs_server.hostname, buf->data, buf->len);
+		cfg->nfs_server.hostname[buf->len] = '\0';
 
-		snprintf(page, PAGE_SIZE, "%s:%s",
-				mountdata->hostname,
-				mountdata->mnt_path);
+		p = cfg->sc.device;
+		memcpy(p, buf->data, buf->len);
+		p += buf->len;
+		*p++ = ':';
+		memcpy(p, cfg->nfs_server.export_path, cfg->nfs_server.export_path_len);
+		p += cfg->nfs_server.export_path_len;
+		*p = 0;
 
-		mnt = vfs_submount(mountdata->dentry, &nfs4_referral_fs_type, page, mountdata);
+		mnt = vfs_submount_sc(cfg->clone_data.dentry, &cfg->sc);
 		if (!IS_ERR(mnt))
 			break;
 	}
-	kfree(mountdata->addr);
 	return mnt;
 }
 
@@ -296,33 +338,43 @@ static struct vfsmount *try_location(struct nfs_clone_mount *mountdata,
 static struct vfsmount *nfs_follow_referral(struct dentry *dentry,
 					    const struct nfs4_fs_locations *locations)
 {
-	struct vfsmount *mnt = ERR_PTR(-ENOENT);
-	struct nfs_clone_mount mountdata = {
-		.sb = dentry->d_sb,
-		.dentry = dentry,
-		.authflavor = NFS_SB(dentry->d_sb)->client->cl_auth->au_flavor,
-	};
-	char *page = NULL, *page2 = NULL;
+	struct nfs_sb_config *cfg;
+	struct sb_config *sc;
+	struct vfsmount *mnt;
+	char *export_path;
 	int loc, error;
 
 	if (locations == NULL || locations->nlocations <= 0)
 		goto out;
 
+	sc = __vfs_new_sb_config(&nfs4_fs_type, dentry->d_sb, 0,
+				 SB_CONFIG_FOR_SUBMOUNT);
+	if (IS_ERR(sc)) {
+		mnt = ERR_CAST(sc);
+		goto out;
+	}
+	cfg = container_of(sc, struct nfs_sb_config, sc);
+
 	dprintk("%s: referral at %pd2\n", __func__, dentry);
 
-	page = (char *) __get_free_page(GFP_USER);
-	if (!page)
-		goto out;
+	cfg->mount_type		= NFS4_MOUNT_REFERRAL;
+	cfg->clone_data.sb	= dentry->d_sb;
+	cfg->clone_data.dentry	= dentry;
+	cfg->clone_data.cloned	= true;
 
-	page2 = (char *) __get_free_page(GFP_USER);
-	if (!page2)
-		goto out;
+	export_path = nfs4_pathname_string(&locations->fs_path,
+					   &cfg->nfs_server.export_path_len);
+	if (IS_ERR(export_path)) {
+		mnt = ERR_CAST(export_path);
+		goto out_sc;
+	}
+	cfg->nfs_server.export_path = export_path;
 
 	/* Ensure fs path is a prefix of current dentry path */
-	error = nfs4_validate_fspath(dentry, locations, page, page2);
+	error = nfs4_validate_fspath(dentry, locations, cfg);
 	if (error < 0) {
 		mnt = ERR_PTR(error);
-		goto out;
+		goto out_sc;
 	}
 
 	for (loc = 0; loc < locations->nlocations; loc++) {
@@ -332,14 +384,14 @@ static struct vfsmount *nfs_follow_referral(struct dentry *dentry,
 		    location->rootpath.ncomponents == 0)
 			continue;
 
-		mnt = try_location(&mountdata, page, page2, location);
+		mnt = try_location(cfg->clone_data.dentry, cfg, location);
 		if (!IS_ERR(mnt))
 			break;
 	}
 
+out_sc:
+	put_sb_config(sc);
 out:
-	free_page((unsigned long) page);
-	free_page((unsigned long) page2);
 	return mnt;
 }
 
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index c08c46a3b8cd..ca792f799941 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -9307,8 +9307,9 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
 	.file_inode_ops	= &nfs4_file_inode_operations,
 	.file_ops	= &nfs4_file_operations,
 	.getroot	= nfs4_proc_get_root,
+	.get_tree	= nfs4_get_tree,
 	.submount	= nfs4_submount,
-	.try_mount	= nfs4_try_mount,
+	.try_get_tree	= nfs4_try_get_tree,
 	.getattr	= nfs4_proc_getattr,
 	.setattr	= nfs4_proc_setattr,
 	.lookup		= nfs4_proc_lookup,
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index d160ccd89865..0a5bc2bed459 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -3,6 +3,7 @@
  */
 #include <linux/init.h>
 #include <linux/module.h>
+#include <linux/mount.h>
 #include <linux/nfs4_mount.h>
 #include <linux/nfs_fs.h>
 #include "delegation.h"
@@ -17,36 +18,6 @@
 
 static int nfs4_write_inode(struct inode *inode, struct writeback_control *wbc);
 static void nfs4_evict_inode(struct inode *inode);
-static struct dentry *nfs4_remote_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *raw_data);
-static struct dentry *nfs4_referral_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *raw_data);
-static struct dentry *nfs4_remote_referral_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *raw_data);
-
-static struct file_system_type nfs4_remote_fs_type = {
-	.owner		= THIS_MODULE,
-	.name		= "nfs4",
-	.mount		= nfs4_remote_mount,
-	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
-};
-
-static struct file_system_type nfs4_remote_referral_fs_type = {
-	.owner		= THIS_MODULE,
-	.name		= "nfs4",
-	.mount		= nfs4_remote_referral_mount,
-	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
-};
-
-struct file_system_type nfs4_referral_fs_type = {
-	.owner		= THIS_MODULE,
-	.name		= "nfs4",
-	.mount		= nfs4_referral_mount,
-	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
-};
 
 static const struct super_operations nfs4_sops = {
 	.alloc_inode	= nfs_alloc_inode,
@@ -60,16 +31,16 @@ static const struct super_operations nfs4_sops = {
 	.show_devname	= nfs_show_devname,
 	.show_path	= nfs_show_path,
 	.show_stats	= nfs_show_stats,
-	.remount_fs	= nfs_remount,
+	.remount_fs_sc	= nfs_remount,
 };
 
 struct nfs_subversion nfs_v4 = {
-	.owner = THIS_MODULE,
-	.nfs_fs   = &nfs4_fs_type,
-	.rpc_vers = &nfs_version4,
-	.rpc_ops  = &nfs_v4_clientops,
-	.sops     = &nfs4_sops,
-	.xattr    = nfs4_xattr_handlers,
+	.owner		= THIS_MODULE,
+	.nfs_fs		= &nfs4_fs_type,
+	.rpc_vers	= &nfs_version4,
+	.rpc_ops	= &nfs_v4_clientops,
+	.sops		= &nfs4_sops,
+	.xattr		= nfs4_xattr_handlers,
 };
 
 static int nfs4_write_inode(struct inode *inode, struct writeback_control *wbc)
@@ -103,47 +74,63 @@ static void nfs4_evict_inode(struct inode *inode)
 /*
  * Get the superblock for the NFS4 root partition
  */
-static struct dentry *
-nfs4_remote_mount(struct file_system_type *fs_type, int flags,
-		  const char *dev_name, void *info)
+static int nfs4_get_remote_tree(struct nfs_sb_config *cfg)
 {
-	struct nfs_mount_info *mount_info = info;
 	struct nfs_server *server;
-	struct dentry *mntroot = ERR_PTR(-ENOMEM);
 
-	mount_info->set_security = nfs_set_sb_security;
+	cfg->set_security = nfs_set_sb_security;
 
 	/* Get a volume representation */
-	server = nfs4_create_server(mount_info, &nfs_v4);
-	if (IS_ERR(server)) {
-		mntroot = ERR_CAST(server);
-		goto out;
-	}
-
-	mntroot = nfs_fs_mount_common(server, flags, dev_name, mount_info, &nfs_v4);
+	server = nfs4_create_server(cfg);
+	if (IS_ERR(server))
+		return PTR_ERR(server);
 
-out:
-	return mntroot;
+	return nfs_get_tree_common(server, cfg);
 }
 
-static struct vfsmount *nfs_do_root_mount(struct file_system_type *fs_type,
-		int flags, void *data, const char *hostname)
+/*
+ * Create a mount for the root of the server.  We copy the mount context we
+ * have for the parameters and set its hostname, path and type.
+ */
+static struct vfsmount *nfs_do_root_mount(struct nfs_sb_config *cfg,
+					  const char *hostname,
+					  enum nfs_mount_type type)
 {
+	struct nfs_sb_config *root_cfg;
+	struct sb_config *root_sc;
 	struct vfsmount *root_mnt;
 	char *root_devname;
 	size_t len;
+	int ret;
+
+	root_sc = vfs_dup_sb_config(&cfg->sc);
+	if (IS_ERR(root_sc))
+		return ERR_CAST(root_sc);
+	root_cfg = container_of(root_sc, struct nfs_sb_config, sc);
+
+	root_cfg->mount_type = type;
+	root_cfg->nfs_server.export_path = (char *)nfs_slash;
 
 	len = strlen(hostname) + 5;
+	root_mnt = ERR_PTR(-ENOMEM);
 	root_devname = kmalloc(len, GFP_KERNEL);
 	if (root_devname == NULL)
-		return ERR_PTR(-ENOMEM);
+		goto out_sc;
+
 	/* Does hostname needs to be enclosed in brackets? */
 	if (strchr(hostname, ':'))
 		snprintf(root_devname, len, "[%s]:/", hostname);
 	else
 		snprintf(root_devname, len, "%s:/", hostname);
-	root_mnt = vfs_kern_mount(fs_type, flags, root_devname, data);
-	kfree(root_devname);
+	root_cfg->sc.device = root_devname;
+
+	ret = vfs_get_tree(&root_cfg->sc);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	root_mnt = vfs_kern_mount_sc(&root_cfg->sc);
+out_sc:
+	put_sb_config(root_sc);
 	return root_mnt;
 }
 
@@ -234,89 +221,98 @@ static struct dentry *nfs_follow_remote_path(struct vfsmount *root_mnt,
 	return dentry;
 }
 
-struct dentry *nfs4_try_mount(int flags, const char *dev_name,
-			      struct nfs_mount_info *mount_info,
-			      struct nfs_subversion *nfs_mod)
+int nfs4_try_get_tree(struct nfs_sb_config *cfg)
 {
-	char *export_path;
 	struct vfsmount *root_mnt;
-	struct dentry *res;
-	struct nfs_sb_config *cfg = mount_info->cfg;
+	struct dentry *root;
 
-	dfprintk(MOUNT, "--> nfs4_try_mount()\n");
+	dfprintk(MOUNT, "--> nfs4_try_get_tree()\n");
 
-	export_path = cfg->nfs_server.export_path;
-	cfg->nfs_server.export_path = "/";
-	root_mnt = nfs_do_root_mount(&nfs4_remote_fs_type, flags, mount_info,
-			cfg->nfs_server.hostname);
-	cfg->nfs_server.export_path = export_path;
-
-	res = nfs_follow_remote_path(root_mnt, export_path);
+	/* We create a mount for the server's root, walk to the requested
+	 * location and then create another mount for that.
+	 */
+	root_mnt = nfs_do_root_mount(cfg, cfg->nfs_server.hostname,
+				     NFS4_MOUNT_REMOTE);
+	if (IS_ERR(root_mnt))
+		return PTR_ERR(root_mnt);
+
+	root = nfs_follow_remote_path(root_mnt, cfg->nfs_server.export_path);
+	if (IS_ERR(root)) {
+		errorf("NFS4: Couldn't follow remote path");
+		dfprintk(MOUNT, "<-- nfs4_try_mount() = %ld [error]\n",
+			 PTR_ERR(root));
+		return PTR_ERR(root);
+	}
 
-	dfprintk(MOUNT, "<-- nfs4_try_mount() = %d%s\n",
-		 PTR_ERR_OR_ZERO(res),
-		 IS_ERR(res) ? " [error]" : "");
-	return res;
+	cfg->sc.root = root;
+	dfprintk(MOUNT, "<-- nfs4_try_get_tree() = 0\n");
+	return 0;
 }
 
-static struct dentry *
-nfs4_remote_referral_mount(struct file_system_type *fs_type, int flags,
-			   const char *dev_name, void *raw_data)
+static int nfs4_get_remote_referral_tree(struct nfs_sb_config *cfg)
 {
-	struct nfs_mount_info mount_info = {
-		.fill_super = nfs_fill_super,
-		.set_security = nfs_clone_sb_security,
-		.cloned = raw_data,
-	};
 	struct nfs_server *server;
-	struct dentry *mntroot = ERR_PTR(-ENOMEM);
 
-	dprintk("--> nfs4_referral_get_sb()\n");
+	dprintk("--> nfs4_get_remote_referral_tree()\n");
 
-	mount_info.mntfh = nfs_alloc_fhandle();
-	if (mount_info.cloned == NULL || mount_info.mntfh == NULL)
-		goto out;
+	cfg->set_security = nfs_clone_sb_security;
+
+	if (!cfg->clone_data.cloned)
+		return -EINVAL;
 
 	/* create a new volume representation */
-	server = nfs4_create_referral_server(mount_info.cloned, mount_info.mntfh);
-	if (IS_ERR(server)) {
-		mntroot = ERR_CAST(server);
-		goto out;
-	}
+	server = nfs4_create_referral_server(cfg);
+	if (IS_ERR(server))
+		return PTR_ERR(server);
 
-	mntroot = nfs_fs_mount_common(server, flags, dev_name, &mount_info, &nfs_v4);
-out:
-	nfs_free_fhandle(mount_info.mntfh);
-	return mntroot;
+	return nfs_get_tree_common(server, cfg);
 }
 
 /*
  * Create an NFS4 server record on referral traversal
  */
-static struct dentry *nfs4_referral_mount(struct file_system_type *fs_type,
-		int flags, const char *dev_name, void *raw_data)
+static int nfs4_get_referral_tree(struct nfs_sb_config *cfg)
 {
-	struct nfs_clone_mount *data = raw_data;
-	char *export_path;
 	struct vfsmount *root_mnt;
-	struct dentry *res;
+	struct dentry *root;
 
 	dprintk("--> nfs4_referral_mount()\n");
 
-	export_path = data->mnt_path;
-	data->mnt_path = "/";
+	root_mnt = nfs_do_root_mount(cfg, cfg->nfs_server.hostname,
+				     NFS4_MOUNT_REMOTE_REFERRAL);
 
-	root_mnt = nfs_do_root_mount(&nfs4_remote_referral_fs_type,
-			flags, data, data->hostname);
-	data->mnt_path = export_path;
+	root = nfs_follow_remote_path(root_mnt, cfg->nfs_server.export_path);
+	if (IS_ERR(root)) {
+		errorf("NFS4: Couldn't follow remote path");
+		dfprintk(MOUNT, "<-- nfs4_referral_mount() = %ld [error]\n",
+			 PTR_ERR(root));
+		return PTR_ERR(root);
+	}
 
-	res = nfs_follow_remote_path(root_mnt, export_path);
-	dprintk("<-- nfs4_referral_mount() = %d%s\n",
-		PTR_ERR_OR_ZERO(res),
-		IS_ERR(res) ? " [error]" : "");
-	return res;
+	cfg->sc.root = root;
+	dfprintk(MOUNT, "<-- nfs4_get_referral_tree() = 0\n");
+	return 0;
 }
 
+/*
+ * Handle special NFS4 mount types.
+ */
+int nfs4_get_tree(struct nfs_sb_config *cfg)
+{
+	switch (cfg->mount_type) {
+	case NFS4_MOUNT_REMOTE:
+		return nfs4_get_remote_tree(cfg);
+
+	case NFS4_MOUNT_REFERRAL:
+		return nfs4_get_referral_tree(cfg);
+
+	case NFS4_MOUNT_REMOTE_REFERRAL:
+		return nfs4_get_remote_referral_tree(cfg);
+
+	default:
+		return 1;
+	}
+}
 
 static int __init init_nfs_v4(void)
 {
diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
index 9872cf676a50..cd22b82e7bb8 100644
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -705,7 +705,7 @@ const struct nfs_rpc_ops nfs_v2_clientops = {
 	.file_ops	= &nfs_file_operations,
 	.getroot	= nfs_proc_get_root,
 	.submount	= nfs_submount,
-	.try_mount	= nfs_try_mount,
+	.try_get_tree	= nfs_try_get_tree,
 	.getattr	= nfs_proc_getattr,
 	.setattr	= nfs_proc_setattr,
 	.lookup		= nfs_proc_lookup,
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index acf935a6438d..34a7c16cb33c 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -69,27 +69,6 @@
 
 #define NFSDBG_FACILITY		NFSDBG_VFS
 
-static struct dentry *nfs_xdev_mount(struct file_system_type *fs_type,
-		int flags, const char *dev_name, void *raw_data);
-
-struct file_system_type nfs_fs_type = {
-	.owner		= THIS_MODULE,
-	.name		= "nfs",
-	.mount		= nfs_fs_mount,
-	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
-};
-MODULE_ALIAS_FS("nfs");
-EXPORT_SYMBOL_GPL(nfs_fs_type);
-
-struct file_system_type nfs_xdev_fs_type = {
-	.owner		= THIS_MODULE,
-	.name		= "nfs",
-	.mount		= nfs_xdev_mount,
-	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
-};
-
 const struct super_operations nfs_sops = {
 	.alloc_inode	= nfs_alloc_inode,
 	.destroy_inode	= nfs_destroy_inode,
@@ -102,22 +81,11 @@ const struct super_operations nfs_sops = {
 	.show_devname	= nfs_show_devname,
 	.show_path	= nfs_show_path,
 	.show_stats	= nfs_show_stats,
-	.remount_fs	= nfs_remount,
+	.remount_fs_sc	= nfs_remount,
 };
 EXPORT_SYMBOL_GPL(nfs_sops);
 
 #if IS_ENABLED(CONFIG_NFS_V4)
-struct file_system_type nfs4_fs_type = {
-	.owner		= THIS_MODULE,
-	.name		= "nfs4",
-	.mount		= nfs_fs_mount,
-	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
-};
-MODULE_ALIAS_FS("nfs4");
-MODULE_ALIAS("nfs4");
-EXPORT_SYMBOL_GPL(nfs4_fs_type);
-
 static int __init register_nfs4_fs(void)
 {
 	return register_filesystem(&nfs4_fs_type);
@@ -772,7 +740,7 @@ static int nfs_request_mount(struct nfs_sb_config *cfg,
 		.noresvport	= cfg->flags & NFS_MOUNT_NORESVPORT,
 		.auth_flav_len	= server_authlist_len,
 		.auth_flavs	= server_authlist,
-		.net		= cfg->net,
+		.net		= cfg->sc.net_ns,
 	};
 	int status;
 
@@ -817,20 +785,17 @@ static int nfs_request_mount(struct nfs_sb_config *cfg,
 	return 0;
 }
 
-static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_info,
-					struct nfs_subversion *nfs_mod)
+static struct nfs_server *nfs_try_mount_request(struct nfs_sb_config *cfg)
 {
 	int status;
 	unsigned int i;
 	bool tried_auth_unix = false;
 	bool auth_null_in_list = false;
 	struct nfs_server *server = ERR_PTR(-EACCES);
-	struct nfs_sb_config *cfg = mount_info->cfg;
 	rpc_authflavor_t authlist[NFS_MAX_SECFLAVORS];
 	unsigned int authlist_len = ARRAY_SIZE(authlist);
 
-	status = nfs_request_mount(cfg, mount_info->mntfh, authlist,
-					&authlist_len);
+	status = nfs_request_mount(cfg, cfg->mntfh, authlist, &authlist_len);
 	if (status)
 		return ERR_PTR(status);
 
@@ -844,7 +809,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 			 cfg->selected_flavor);
 		if (status)
 			return ERR_PTR(status);
-		return nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
+		return cfg->nfs_mod->rpc_ops->create_server(cfg);
 	}
 
 	/*
@@ -871,7 +836,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 		}
 		dfprintk(MOUNT, "NFS: attempting to use auth flavor %u\n", flavor);
 		cfg->selected_flavor = flavor;
-		server = nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
+		server = cfg->nfs_mod->rpc_ops->create_server(cfg);
 		if (!IS_ERR(server))
 			return server;
 	}
@@ -887,26 +852,27 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
 	/* Last chance! Try AUTH_UNIX */
 	dfprintk(MOUNT, "NFS: attempting to use auth flavor %u\n", RPC_AUTH_UNIX);
 	cfg->selected_flavor = RPC_AUTH_UNIX;
-	return nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
+	return cfg->nfs_mod->rpc_ops->create_server(cfg);
 }
 
-struct dentry *nfs_try_mount(int flags, const char *dev_name,
-			     struct nfs_mount_info *mount_info,
-			     struct nfs_subversion *nfs_mod)
+int nfs_try_get_tree(struct nfs_sb_config *cfg)
 {
 	struct nfs_server *server;
 
-	if (mount_info->cfg->need_mount)
-		server = nfs_try_mount_request(mount_info, nfs_mod);
+	if (cfg->need_mount)
+		server = nfs_try_mount_request(cfg);
 	else
-		server = nfs_mod->rpc_ops->create_server(mount_info, nfs_mod);
+		server = cfg->nfs_mod->rpc_ops->create_server(cfg);
 
-	if (IS_ERR(server))
-		return ERR_CAST(server);
+	if (IS_ERR(server)) {
+		errorf("NFS: Couldn't create server");
+		return PTR_ERR(server);
+	}
 
-	return nfs_fs_mount_common(server, flags, dev_name, mount_info, nfs_mod);
+	return nfs_get_tree_common(server, cfg);
 }
-EXPORT_SYMBOL_GPL(nfs_try_mount);
+EXPORT_SYMBOL_GPL(nfs_try_get_tree);
+
 
 #define NFS_REMOUNT_CMP_FLAGMASK ~(NFS_MOUNT_INTR \
 		| NFS_MOUNT_SECURE \
@@ -946,15 +912,11 @@ nfs_compare_remount_data(struct nfs_server *nfss,
 	return 0;
 }
 
-int
-nfs_remount(struct super_block *sb, int *flags, char *raw_data)
+int nfs_remount(struct super_block *sb, struct sb_config *sc)
 {
-	int error;
+	struct nfs_sb_config *cfg =
+		container_of(sc, struct nfs_sb_config, sc);
 	struct nfs_server *nfss = sb->s_fs_info;
-	struct nfs_sb_config *cfg;
-	struct nfs_mount_data *options = (struct nfs_mount_data *)raw_data;
-	struct nfs4_mount_data *options4 = (struct nfs4_mount_data *)raw_data;
-	u32 nfsvers = nfss->nfs_client->rpc_ops->version;
 
 	sync_filesystem(sb);
 
@@ -964,39 +926,9 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)
 	 * ones were explicitly specified. Fall back to legacy behavior and
 	 * just return success.
 	 */
-	if ((nfsvers == 4 && (!options4 || options4->version == 1)) ||
-	    (nfsvers <= 3 && (!options || (options->version >= 1 &&
-					   options->version <= 6))))
+	if (cfg->skip_remount_option_check)
 		return 0;
 
-	cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
-	if (cfg == NULL)
-		return -ENOMEM;
-
-	/* fill out struct with values from existing mount */
-	cfg->flags = nfss->flags;
-	cfg->rsize = nfss->rsize;
-	cfg->wsize = nfss->wsize;
-	cfg->retrans = nfss->client->cl_timeout->to_retries;
-	cfg->selected_flavor = nfss->client->cl_auth->au_flavor;
-	cfg->acregmin = nfss->acregmin / HZ;
-	cfg->acregmax = nfss->acregmax / HZ;
-	cfg->acdirmin = nfss->acdirmin / HZ;
-	cfg->acdirmax = nfss->acdirmax / HZ;
-	cfg->timeo = 10U * nfss->client->cl_timeout->to_initval / HZ;
-	cfg->nfs_server.port = nfss->port;
-	cfg->nfs_server.addrlen = nfss->nfs_client->cl_addrlen;
-	cfg->version = nfsvers;
-	cfg->minorversion = nfss->nfs_client->cl_minorversion;
-	cfg->net = current->nsproxy->net_ns;
-	memcpy(&cfg->nfs_server.address, &nfss->nfs_client->cl_addr,
-		cfg->nfs_server.addrlen);
-
-	/* overwrite those values with any that were specified */
-	error = -EINVAL;
-	if (!nfs_parse_mount_options((char *)options, cfg))
-		goto out;
-
 	/*
 	 * noac is a special case. It implies -o sync, but that's not
 	 * necessarily reflected in the mtab options. do_remount_sb
@@ -1004,20 +936,17 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)
 	 * remount options, so we have to explicitly reset it.
 	 */
 	if (cfg->flags & NFS_MOUNT_NOAC)
-		*flags |= MS_SYNCHRONOUS;
+		cfg->sc.ms_flags |= MS_SYNCHRONOUS;
 
 	/* compare new mount options with old ones */
-	error = nfs_compare_remount_data(nfss, cfg);
-out:
-	kfree(cfg);
-	return error;
+	return nfs_compare_remount_data(nfss, cfg);
 }
 EXPORT_SYMBOL_GPL(nfs_remount);
 
 /*
  * Initialise the common bits of the superblock
  */
-inline void nfs_initialise_sb(struct super_block *sb)
+static inline void nfs_initialise_sb(struct super_block *sb)
 {
 	struct nfs_server *server = NFS_SB(sb);
 
@@ -1037,9 +966,8 @@ inline void nfs_initialise_sb(struct super_block *sb)
 /*
  * Finish setting up an NFS2/3 superblock
  */
-void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
+static void nfs_fill_super(struct super_block *sb, struct nfs_sb_config *cfg)
 {
-	struct nfs_sb_config *cfg = mount_info->cfg;
 	struct nfs_server *server = NFS_SB(sb);
 
 	sb->s_blocksize_bits = 0;
@@ -1059,14 +987,13 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
 
  	nfs_initialise_sb(sb);
 }
-EXPORT_SYMBOL_GPL(nfs_fill_super);
 
 /*
  * Finish setting up a cloned NFS2/3/4 superblock
  */
-void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
+static void nfs_clone_super(struct super_block *sb, struct nfs_sb_config *cfg)
 {
-	const struct super_block *old_sb = mount_info->cloned->sb;
+	const struct super_block *old_sb = cfg->clone_data.sb;
 	struct nfs_server *server = NFS_SB(sb);
 
 	sb->s_blocksize_bits = old_sb->s_blocksize_bits;
@@ -1198,8 +1125,7 @@ static int nfs_compare_super(struct super_block *sb, void *data)
 
 #ifdef CONFIG_NFS_FSCACHE
 static void nfs_get_cache_cookie(struct super_block *sb,
-				 struct nfs_sb_config *cfg,
-				 struct nfs_clone_mount *cloned)
+				 struct nfs_sb_config *cfg)
 {
 	struct nfs_server *nfss = NFS_SB(sb);
 	char *uniq = NULL;
@@ -1208,75 +1134,74 @@ static void nfs_get_cache_cookie(struct super_block *sb,
 	nfss->fscache_key = NULL;
 	nfss->fscache = NULL;
 
-	if (cfg) {
+	if (!cfg)
+		return;
+
+	if (cfg->clone_data.cloned) {
+		struct nfs_server *mnt_s = NFS_SB(cfg->clone_data.sb);
+		if (!(mnt_s->options & NFS_OPTION_FSCACHE))
+			return;
+		if (mnt_s->fscache_key) {
+			uniq = mnt_s->fscache_key->key.uniquifier;
+			ulen = mnt_s->fscache_key->key.uniq_len;
+		}
+	} else {
 		if (!(cfg->options & NFS_OPTION_FSCACHE))
 			return;
 		if (cfg->fscache_uniq) {
 			uniq = cfg->fscache_uniq;
 			ulen = strlen(cfg->fscache_uniq);
 		}
-	} else if (cloned) {
-		struct nfs_server *mnt_s = NFS_SB(cloned->sb);
-		if (!(mnt_s->options & NFS_OPTION_FSCACHE))
-			return;
-		if (mnt_s->fscache_key) {
-			uniq = mnt_s->fscache_key->key.uniquifier;
-			ulen = mnt_s->fscache_key->key.uniq_len;
-		};
-	} else
 		return;
+	}
 
 	nfs_fscache_get_super_cookie(sb, uniq, ulen);
 }
 #else
 static void nfs_get_cache_cookie(struct super_block *sb,
-				 struct nfs_sb_config *parsed,
-				 struct nfs_clone_mount *cloned)
+				 struct nfs_sb_config *cfg)
 {
 }
 #endif
 
-int nfs_set_sb_security(struct super_block *s, struct dentry *mntroot,
-			struct nfs_mount_info *mount_info)
+int nfs_set_sb_security(struct super_block *sb, struct nfs_sb_config *cfg)
 {
 	int error;
 	unsigned long kflags = 0, kflags_out = 0;
-	if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL)
+
+	if (NFS_SB(sb)->caps & NFS_CAP_SECURITY_LABEL)
 		kflags |= SECURITY_LSM_NATIVE_LABELS;
 
-	error = security_sb_set_mnt_opts(s, &mount_info->cfg->lsm_opts,
-						kflags, &kflags_out);
+	error = security_sb_set_mnt_opts(sb, cfg->sc.security,
+					 kflags, &kflags_out);
 	if (error)
 		goto err;
 
-	if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL &&
-		!(kflags_out & SECURITY_LSM_NATIVE_LABELS))
-		NFS_SB(s)->caps &= ~NFS_CAP_SECURITY_LABEL;
+	if (NFS_SB(sb)->caps & NFS_CAP_SECURITY_LABEL &&
+	    !(kflags_out & SECURITY_LSM_NATIVE_LABELS))
+		NFS_SB(sb)->caps &= ~NFS_CAP_SECURITY_LABEL;
 err:
 	return error;
 }
 EXPORT_SYMBOL_GPL(nfs_set_sb_security);
 
-int nfs_clone_sb_security(struct super_block *s, struct dentry *mntroot,
-			  struct nfs_mount_info *mount_info)
+int nfs_clone_sb_security(struct super_block *sb, struct nfs_sb_config *cfg)
 {
 	/* clone any lsm security options from the parent to the new sb */
-	if (d_inode(mntroot)->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops)
+	if (d_inode(cfg->sc.root)->i_op !=
+	    NFS_SB(sb)->nfs_client->rpc_ops->dir_inode_ops)
 		return -ESTALE;
-	return security_sb_clone_mnt_opts(mount_info->cloned->sb, s);
+	return security_sb_clone_mnt_opts(cfg->clone_data.sb, sb);
 }
 EXPORT_SYMBOL_GPL(nfs_clone_sb_security);
 
-struct dentry *nfs_fs_mount_common(struct nfs_server *server,
-				   int flags, const char *dev_name,
-				   struct nfs_mount_info *mount_info,
-				   struct nfs_subversion *nfs_mod)
+int nfs_get_tree_common(struct nfs_server *server, struct nfs_sb_config *cfg)
 {
 	struct super_block *s;
 	struct dentry *mntroot = ERR_PTR(-ENOMEM);
 	int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
 	struct nfs_sb_mountdata sb_mntdata = {
-		.mntflags = flags,
+		.mntflags = cfg->sc.ms_flags,
 		.server = server,
 	};
 	int error;
@@ -1288,14 +1213,16 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
 	if (server->flags & NFS_MOUNT_NOAC)
 		sb_mntdata.mntflags |= MS_SYNCHRONOUS;
 
-	if (mount_info->cloned != NULL && mount_info->cloned->sb != NULL)
-		if (mount_info->cloned->sb->s_flags & MS_SYNCHRONOUS)
+	if (cfg->clone_data.cloned && cfg->clone_data.sb != NULL)
+		if (cfg->clone_data.sb->s_flags & MS_SYNCHRONOUS)
 			sb_mntdata.mntflags |= MS_SYNCHRONOUS;
 
 	/* Get a superblock - note that we may end up sharing one that already exists */
-	s = sget(nfs_mod->nfs_fs, compare_super, nfs_set_super, flags, &sb_mntdata);
+	s = sget(cfg->nfs_mod->nfs_fs, compare_super, nfs_set_super, cfg->sc.ms_flags,
+		 &sb_mntdata);
 	if (IS_ERR(s)) {
-		mntroot = ERR_CAST(s);
+		error = PTR_ERR(s);
+		errorf("NFS: Couldn't get superblock");
 		goto out_err_nosb;
 	}
 
@@ -1315,22 +1242,28 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
 
 	if (!s->s_root) {
 		/* initial superblock/root creation */
-		mount_info->fill_super(s, mount_info);
-		nfs_get_cache_cookie(s, mount_info->cfg, mount_info->cloned);
+		if (cfg->clone_data.sb)
+			nfs_clone_super(s, cfg);
+		else
+			nfs_fill_super(s, cfg);
+		nfs_get_cache_cookie(s, cfg);
 	}
 
-	mntroot = nfs_get_root(s, mount_info->mntfh, dev_name);
-	if (IS_ERR(mntroot))
+	error = nfs_get_root(s, cfg);
+	if (error < 0) {
+		errorf("NFS: Couldn't get root dentry");
 		goto error_splat_super;
+	}
 
-	error = mount_info->set_security(s, mntroot, mount_info);
+	error = cfg->set_security(s, cfg);
 	if (error)
 		goto error_splat_root;
 
 	s->s_flags |= MS_ACTIVE;
+	error = 0;
 
 out:
-	return mntroot;
+	return error;
 
 out_err_nosb:
 	nfs_free_server(server);
@@ -1338,53 +1271,11 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
 
 error_splat_root:
 	dput(mntroot);
-	mntroot = ERR_PTR(error);
 error_splat_super:
 	deactivate_locked_super(s);
 	goto out;
 }
-EXPORT_SYMBOL_GPL(nfs_fs_mount_common);
-
-struct dentry *nfs_fs_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *raw_data)
-{
-	struct nfs_mount_info mount_info = {
-		.fill_super = nfs_fill_super,
-		.set_security = nfs_set_sb_security,
-	};
-	struct dentry *mntroot = ERR_PTR(-ENOMEM);
-	struct nfs_subversion *nfs_mod;
-	int error;
-
-	mount_info.cfg = nfs_alloc_parsed_mount_data();
-	mount_info.mntfh = nfs_alloc_fhandle();
-	if (mount_info.cfg == NULL || mount_info.mntfh == NULL)
-		goto out;
-
-	/* Validate the mount data */
-	error = nfs_validate_mount_data(fs_type, raw_data, mount_info.cfg, mount_info.mntfh, dev_name);
-	if (error == NFS_TEXT_DATA)
-		error = nfs_validate_text_mount_data(raw_data, mount_info.cfg, dev_name);
-	if (error < 0) {
-		mntroot = ERR_PTR(error);
-		goto out;
-	}
-
-	nfs_mod = get_nfs_version(mount_info.cfg->version);
-	if (IS_ERR(nfs_mod)) {
-		mntroot = ERR_CAST(nfs_mod);
-		goto out;
-	}
-
-	mntroot = nfs_mod->rpc_ops->try_mount(flags, dev_name, &mount_info, nfs_mod);
-
-	put_nfs_version(nfs_mod);
-out:
-	nfs_free_parsed_mount_data(mount_info.cfg);
-	nfs_free_fhandle(mount_info.mntfh);
-	return mntroot;
-}
-EXPORT_SYMBOL_GPL(nfs_fs_mount);
+EXPORT_SYMBOL_GPL(nfs_get_tree_common);
 
 /*
  * Destroy an NFS2/3 superblock
@@ -1403,41 +1294,6 @@ void nfs_kill_super(struct super_block *s)
 }
 EXPORT_SYMBOL_GPL(nfs_kill_super);
 
-/*
- * Clone an NFS2/3/4 server record on xdev traversal (FSID-change)
- */
-static struct dentry *
-nfs_xdev_mount(struct file_system_type *fs_type, int flags,
-		const char *dev_name, void *raw_data)
-{
-	struct nfs_clone_mount *data = raw_data;
-	struct nfs_mount_info mount_info = {
-		.fill_super = nfs_clone_super,
-		.set_security = nfs_clone_sb_security,
-		.cloned = data,
-	};
-	struct nfs_server *server;
-	struct dentry *mntroot = ERR_PTR(-ENOMEM);
-	struct nfs_subversion *nfs_mod = NFS_SB(data->sb)->nfs_client->cl_nfs_mod;
-
-	dprintk("--> nfs_xdev_mount()\n");
-
-	mount_info.mntfh = mount_info.cloned->fh;
-
-	/* create a new volume representation */
-	server = nfs_mod->rpc_ops->clone_server(NFS_SB(data->sb), data->fh, data->fattr, data->authflavor);
-
-	if (IS_ERR(server))
-		mntroot = ERR_CAST(server);
-	else
-		mntroot = nfs_fs_mount_common(server, flags,
-				dev_name, &mount_info, nfs_mod);
-
-	dprintk("<-- nfs_xdev_mount() = %ld\n",
-			IS_ERR(mntroot) ? PTR_ERR(mntroot) : 0L);
-	return mntroot;
-}
-
 #if IS_ENABLED(CONFIG_NFS_V4)
 
 /*
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index b28c83475ee8..7804241739a0 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -1542,6 +1542,7 @@ struct nfs_subversion;
 struct nfs_mount_info;
 struct nfs_client_initdata;
 struct nfs_pageio_descriptor;
+struct nfs_sb_config;
 
 /*
  * RPC procedure vector for NFSv2/NFSv3 demuxing
@@ -1556,10 +1557,10 @@ struct nfs_rpc_ops {
 
 	int	(*getroot) (struct nfs_server *, struct nfs_fh *,
 			    struct nfs_fsinfo *);
+	int	(*get_tree)(struct nfs_sb_config *);
 	struct vfsmount *(*submount) (struct nfs_server *, struct dentry *,
 				      struct nfs_fh *, struct nfs_fattr *);
-	struct dentry *(*try_mount) (int, const char *, struct nfs_mount_info *,
-				     struct nfs_subversion *);
+	int	(*try_get_tree) (struct nfs_sb_config *);
 	int	(*getattr) (struct nfs_server *, struct nfs_fh *,
 			    struct nfs_fattr *, struct nfs4_label *);
 	int	(*setattr) (struct dentry *, struct nfs_fattr *,
@@ -1620,7 +1621,7 @@ struct nfs_rpc_ops {
 	struct nfs_client *(*init_client) (struct nfs_client *,
 				const struct nfs_client_initdata *);
 	void	(*free_client) (struct nfs_client *);
-	struct nfs_server *(*create_server)(struct nfs_mount_info *, struct nfs_subversion *);
+	struct nfs_server *(*create_server)(struct nfs_sb_config *);
 	struct nfs_server *(*clone_server)(struct nfs_server *, struct nfs_fh *,
 					   struct nfs_fattr *, rpc_authflavor_t);
 };

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* Re: [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (22 preceding siblings ...)
  2017-05-22 15:54 ` [PATCH 23/23] NFS: Add sb_config support. " David Howells
@ 2017-05-30 14:50 ` Al Viro
  2017-05-30 15:36 ` David Howells
                   ` (2 subsequent siblings)
  26 siblings, 0 replies; 30+ messages in thread
From: Al Viro @ 2017-05-30 14:50 UTC (permalink / raw)
  To: David Howells; +Cc: mszeredi, jlayton, linux-fsdevel, linux-nfs, linux-kernel

On Mon, May 22, 2017 at 04:50:56PM +0100, David Howells wrote:
> 
> Here are a set of patches to create a superblock configuration context
> prior to setting up a new mount, populating it with the parsed
> options/binary data, creating the superblock and then effecting the mount.
> 
> This allows namespaces and other information to be conveyed through the
> mount procedure.  It also allows extra error information to be returned
> (so many things can go wrong during a mount that a small integer isn't
> really sufficient to convey the issue).
> 
> This also allows Miklós Szeredi's idea of doing:
> 
> 	fd = fsopen("nfs");
> 	write(fd, "option=val", ...);
> 	fsmount(fd, "/mnt");
> 
> that he presented at LSF-2017 to be implemented (see the relevant patches
> in the series), to which I can add:
> 
> 	read(fd, error_buffer, ...);
> 
> to read back any error message.  I didn't use netlink as that would make it
> depend on CONFIG_NET and would introduce network namespacing issues.

Random notes:
	* "sb_config" looks rather odd in the current variant; mount_context,
perhaps?  Or fs_context, for that matter...  Anyway, that's trivial.
	* if NFS folks want to play with EXPORT_SYMBOL_GPL, fine, but any
EXPORT_SYMBOL_GPL in vfs proper is a mistake.  If it's an interface that
makes sense, just export it; if it's a vewwwy, vewwwy pwiwate interface
for some specific module - let's figure out how to deal with that layering
violation rather than exporting it at all.
	* what the hell is ms_flags thing doing in __vfs_new_sb_config()?
It's a really vile mix of unrelated flags and operations we had in existing
mount(2) ABI.  With MS_KERNMOUNT thrown into that loo^Wmix.  Sure, we need
to parse the garbage fed to mount(2).  And we need to pass that garbage to
"legacy" types as well, but let's not inflict it upon the new mechanisms.
	* what's wrong with simple_pin_fs() as it is?  You keep
vfs_kern_mount() anyway, so...
	* vfs_new_sb_config(): please, move dealing with name into the caller.
Then you would be able to use it more than once.
	* submount side of that thing: do we ever want a type different from
that of src_sb, and how the fuck would methods know what to do with it?
	* remounts: where (if anywhere) do you call ->validate() for those,
and if you do not, WTF is this
+       if (cfg->sc.purpose == SB_CONFIG_FOR_REMOUNT)
+               return 0;
for?  You know, the only place that ever looks at ->purpose...
	* docs need to be brought in sync with code - 'purpose' is called 'mount_type'
in those, which is especially unpleasant since you do introduce a field called just
that - NFS-only and in NFS-private part.
	* you don't need to register filesystem to use kern_mount()
	* locking inode in fsmount(2).  What for?
	* ->sb_mountpoint().  YALinuxSadoMasochismHook.  Not called on normal
mount(2) pathway.  Yuck...
	* could you split whitespace parts off?  Minor, but...
	* I'd like to see ipc/mqueue.c dealt with as well; feels like procfs
counterpart might have too much open-coded.  This would show what might be
folded into saner helpers...

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (23 preceding siblings ...)
  2017-05-30 14:50 ` [RFC][PATCH 00/23] VFS: Introduce superblock configuration context " Al Viro
@ 2017-05-30 15:36 ` David Howells
  2017-05-31  7:51   ` Miklos Szeredi
  2017-06-02 10:14 ` David Howells
  2017-06-09  7:48 ` Some filesystems set MNT_* flags in superblock->s_flags David Howells
  26 siblings, 1 reply; 30+ messages in thread
From: David Howells @ 2017-05-30 15:36 UTC (permalink / raw)
  To: Al Viro
  Cc: dhowells, mszeredi, jlayton, linux-fsdevel, linux-nfs, linux-kernel

Al Viro <viro@ZenIV.linux.org.uk> wrote:

> Random notes:
> 	* "sb_config" looks rather odd in the current variant; mount_context,
> perhaps?  Or fs_context, for that matter...  Anyway, that's trivial.

You can argue that one with Miklós.  He argued against mount_context as I had
it originally.  His point is that the same struct may be used when
reconfiguring an sb - which isn't exactly a mount operation (even though we do
it that day today with remount).

> 	* if NFS folks want to play with EXPORT_SYMBOL_GPL, fine, but any
> EXPORT_SYMBOL_GPL in vfs proper is a mistake.  If it's an interface that
> makes sense, just export it; if it's a vewwwy, vewwwy pwiwate interface
> for some specific module - let's figure out how to deal with that layering
> violation rather than exporting it at all.

I agree, but apparently not everyone does.  There are _GPL symbols in the core
VFS that I need to replace.

> 	* what the hell is ms_flags thing doing in __vfs_new_sb_config()?
> It's a really vile mix of unrelated flags and operations we had in existing
> mount(2) ABI.  With MS_KERNMOUNT thrown into that loo^Wmix.  Sure, we need
> to parse the garbage fed to mount(2).  And we need to pass that garbage to
> "legacy" types as well, but let's not inflict it upon the new mechanisms.

I know, but we might get it from mount(2).  I can tamp down the flag mask and
translate it from MS_*, but the MS_* flags are also stored in the superblock
(->s_flags).

I've removed the MNT_* flags from there already.

> 	* what's wrong with simple_pin_fs() as it is?  You keep
> vfs_kern_mount() anyway, so...

I would like to replace vfs_kern_mount() and vfs_submount(), with the _sc
versions but the users would need converting first.  It might make sense to
retain an __init variant of the former though.

> 	* vfs_new_sb_config(): please, move dealing with name into the caller.
> Then you would be able to use it more than once.

Technically, it's used twice, but okay.  I guess I should just rename
__vfs_new_sb_config() to vfs_new_sb_config() and add the extra parameters to
the caller.

> 	* submount side of that thing: do we ever want a type different from
> that of src_sb,

Hmmm...  Good question.  For the moment I've assumed not.  I've killed off the
NFS special types since I can now carry the information in the sb_config
struct that they previously conveyed.

> and how the fuck would methods know what to do with it?

Until I have an example, it's hard to say.
 
> 	* remounts: where (if anywhere) do you call ->validate() for those,

It got moved out of the path that revalidate was invoking.  I need to put it
back.

However, it may be worth leaving this to the filesystem to invoke during
->get_tree() and ->remount_fs() as it then has access to the on-disk fs
metadata if a blockdev is being used, against which it may need to do
validation.

The biggest advantage of having a separate call is that the argument
combination can be validated before taking any locks, opening a blockdev or
sending packets on the network.

> and if you do not, WTF is this
> +       if (cfg->sc.purpose == SB_CONFIG_FOR_REMOUNT)
> +               return 0;
> for?  You know, the only place that ever looks at ->purpose...

That being the only place is true at the moment, but may not remain so as more
filesystems are converted.

> 	* docs need to be brought in sync with code - 'purpose' is called 'mount_type'
> in those, which is especially unpleasant since you do introduce a field called just
> that - NFS-only and in NFS-private part.

Yep.

> 	* you don't need to register filesystem to use kern_mount()

Hmmm...  I'm not sure whether that's actually a problem.

> 	* locking inode in fsmount(2).  What for?

Yeah, I can get rid of that.  The superblock-getting bit used to be done after
this point, so the lock was necessary to prevent a race.

> 	* ->sb_mountpoint().  YALinuxSadoMasochismHook.  Not called on normal
> mount(2) pathway.  Yuck...

That replaces security_sb_kern_mount().  That should move into
do_new_mount_sc().

> 	* could you split whitespace parts off?  Minor, but...

You mean patch 2?  You could just take that one patch and apply it/pass it to
Linus, then I could rebase.

> 	* I'd like to see ipc/mqueue.c dealt with as well; feels like procfs
> counterpart might have too much open-coded.  This would show what might be
> folded into saner helpers...

Okay.  Any other file system types you'd like to see done immediately?
cpuset, maybe?

I still have to finish the ext4 conversion too.

David

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4]
  2017-05-30 15:36 ` David Howells
@ 2017-05-31  7:51   ` Miklos Szeredi
  0 siblings, 0 replies; 30+ messages in thread
From: Miklos Szeredi @ 2017-05-31  7:51 UTC (permalink / raw)
  To: David Howells; +Cc: Al Viro, Jeff Layton, linux-fsdevel, linux-nfs, lkml

On Tue, May 30, 2017 at 5:36 PM, David Howells <dhowells@redhat.com> wrote:
> Al Viro <viro@ZenIV.linux.org.uk> wrote:
>
>> Random notes:
>>       * "sb_config" looks rather odd in the current variant; mount_context,
>> perhaps?  Or fs_context, for that matter...  Anyway, that's trivial.
>
> You can argue that one with Miklós.  He argued against mount_context as I had
> it originally.  His point is that the same struct may be used when
> reconfiguring an sb - which isn't exactly a mount operation (even though we do
> it that day today with remount).

I'm fine with fs_context, just leave "mount" out of this.  Mounting is
the act of attaching the root dentry to the mountpoint. fs_context is
used for obtaining the root, but not for mounting itself, hence
mount_context is misleading IMO.

>>       * what the hell is ms_flags thing doing in __vfs_new_sb_config()?
>> It's a really vile mix of unrelated flags and operations we had in existing
>> mount(2) ABI.  With MS_KERNMOUNT thrown into that loo^Wmix.  Sure, we need
>> to parse the garbage fed to mount(2).  And we need to pass that garbage to
>> "legacy" types as well, but let's not inflict it upon the new mechanisms.
>
> I know, but we might get it from mount(2).  I can tamp down the flag mask and
> translate it from MS_*, but the MS_* flags are also stored in the superblock
> (->s_flags).

Good cleanup would be to just introduce a new set of flags for s_flags
and leave MS_* usage solely for the legacy mount(2) interface.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4]
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (24 preceding siblings ...)
  2017-05-30 15:36 ` David Howells
@ 2017-06-02 10:14 ` David Howells
  2017-06-09  7:48 ` Some filesystems set MNT_* flags in superblock->s_flags David Howells
  26 siblings, 0 replies; 30+ messages in thread
From: David Howells @ 2017-06-02 10:14 UTC (permalink / raw)
  To: Al Viro
  Cc: dhowells, mszeredi, jlayton, linux-fsdevel, linux-nfs, linux-kernel

Hi Al,

Here are some changes I've made, based on your comments plus a little more:

 (*) Get rid of the old vfs_new_sb_config() and rename
     __vfs_new_sb_config() to that.  The callers then have to provide a
     file_system_type pointer and all the args.

 (*) Add a "struct net *s_net_ns" to struct super_block and add a
     FS_IS_NETFS filesystem flag to indicate that this is a network
     filesystem and that s_net_ns should point to a network namespace (it
     should be NULL otherwise).

     [*] Make alloc_super() take an sb_config.

     [*] Provide an sget_sc() that takes an sb_config rather than type,
     	 flags and data.

     [*] sget_sc() sets the type, flags and namespaces from the sb_config
     	 and requires the superblock it's searching for to match both
     	 s_user_ns and s_net_ns.

     [*] The compare and set functions used by sget_sc() take an sb_config
     	 rather than a void* data pointer.

     [*] The xprt_net check in nfs_compare_super_address() is then
     	 superfluous.

     [*] An nfs_server pointer is added to struct nfs_sb_config rendering
     	 struct nfs_sb_mountdata superfluous.

 (*) Make vfs_new_sb_config() get the user_ns and net_ns from a source
     appropriate to the purpose flag: the current->nsproxy if new, the
     reference sb if submount and none if remount.

 (*) Call ->validate() in do_remount().  Ideally, I should be able to make
     do_remount() use the sb_config path unconditionally, but for
     mount_single().  Why does mount_single() remount the fs if it already
     exists?

 (*) Get rid of vfs_submount_sc() and just call vfs_kern_mount_sc()
     directly, having passed SB_CONFIG_FOR_SUBMOUNT to vfs_new_sb_config().

     [*] Don't set MS_SUBMOUNT in this path as it's only used as a
     	 permissions skip - and we can just check the purpose.

     [*] The user_ns check in vfs_submount_sc() is no longer required as
     	 sb_config carries the user_ns information.

 (*) Removed the inode locking from sys_fsmount().

 (*) Moved the security_sb_mountpoint() call into do_new_mount_sc().

What I haven't done yet:

 (*) Renamed sb_config -> fs_context.  I'd rather avoid renaming again
     unless I really have to.

 (*) Convert MS_* to SB_* flags in s_flags and weed out things that don't
     actually go in there (like MS_SUBMOUNT).  This needs to be a separate
     patch.

 (*) Updated the docs.

 (*) Other filesystem conversions.

David
---
diff --git a/fs/fsopen.c b/fs/fsopen.c
index cbede77158ba..2787792d1fc1 100644
--- a/fs/fsopen.c
+++ b/fs/fsopen.c
@@ -222,6 +222,7 @@ fs_initcall(init_fs_fs);
 SYSCALL_DEFINE3(fsopen, const char __user *, _fs_name, int, reserved,
 		unsigned int, flags)
 {
+	struct file_system_type *fs_type;
 	struct sb_config *sc;
 	struct file *file;
 	const char *fs_name;
@@ -234,8 +235,13 @@ SYSCALL_DEFINE3(fsopen, const char __user *, _fs_name, int, reserved,
 	if (IS_ERR(fs_name))
 		return PTR_ERR(fs_name);
 
-	sc = vfs_new_sb_config(fs_name);
+	fs_type = get_fs_type(fs_name);
 	kfree(fs_name);
+	if (!fs_type)
+		return -ENODEV;
+
+	sc = vfs_new_sb_config(fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
+	put_filesystem(fs_type);
 	if (IS_ERR(sc))
 		return PTR_ERR(sc);
 
diff --git a/fs/libfs.c b/fs/libfs.c
index e8787adf0363..eaaab3b3b820 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -583,7 +583,7 @@ int simple_pin_fs(struct file_system_type *type, struct vfsmount **mount, int *c
 	if (unlikely(!*mount)) {
 		spin_unlock(&pin_fs_lock);
 
-		sc = __vfs_new_sb_config(type, NULL, MS_KERNMOUNT, SB_CONFIG_FOR_NEW);
+		sc = vfs_new_sb_config(type, NULL, MS_KERNMOUNT, SB_CONFIG_FOR_NEW);
 		if (IS_ERR(sc))
 			return PTR_ERR(sc);
 
diff --git a/fs/namespace.c b/fs/namespace.c
index 38ee00e8a45d..083ea719383a 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2297,6 +2297,12 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
 		err = parse_monolithic_mount_data(sc, data);
 		if (err < 0)
 			goto err_sc;
+
+		if (sc->ops->validate) {
+			err = sc->ops->validate(sc);
+			if (err < 0)
+				goto err_sc;
+		}
 	} else {
 		err = security_sb_remount(sb, data);
 		if (err)
@@ -2460,6 +2466,10 @@ static int do_new_mount_sc(struct sb_config *sc, struct path *mountpoint,
 	struct vfsmount *mnt;
 	int ret;
 
+	ret = security_sb_mountpoint(sc, mountpoint);
+	if (ret < 0)
+		return ret;;
+
 	mnt = vfs_kern_mount_sc(sc);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
@@ -2486,21 +2496,28 @@ static int do_new_mount_sc(struct sb_config *sc, struct path *mountpoint,
  * create a new mount for userspace and request it to be added into the
  * namespace's tree
  */
-static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
-			int mnt_flags, const char *name, void *data)
+static int do_new_mount(struct path *mountpoint, const char *fstype,
+			int ms_flags, int mnt_flags, const char *name,
+			void *data)
 {
+	struct file_system_type *fs_type;
 	struct sb_config *sc;
-	int err;
+	int err = -EINVAL;
 
 	if (!fstype)
-		return -EINVAL;
+		goto err;
+
+	err = -ENODEV;
+	fs_type = get_fs_type(fstype);
+	if (!fs_type)
+		goto err;
 
-	sc = vfs_new_sb_config(fstype);
+	sc = vfs_new_sb_config(fs_type, NULL, ms_flags, SB_CONFIG_FOR_NEW);
+	put_filesystem(fs_type);
 	if (IS_ERR(sc)) {
 		err = PTR_ERR(sc);
 		goto err;
 	}
-	sc->ms_flags = flags;
 
 	err = -ENOMEM;
 	sc->device = kstrdup(name, GFP_KERNEL);
@@ -3174,7 +3191,7 @@ struct vfsmount *vfs_kern_mount(struct file_system_type *type,
 	if (!type)
 		return ERR_PTR(-EINVAL);
 
-	sc = __vfs_new_sb_config(type, NULL, flags, SB_CONFIG_FOR_NEW);
+	sc = vfs_new_sb_config(type, NULL, flags, SB_CONFIG_FOR_NEW);
 	if (IS_ERR(sc))
 		return ERR_CAST(sc);
 
@@ -3209,21 +3226,6 @@ struct vfsmount *vfs_kern_mount(struct file_system_type *type,
 EXPORT_SYMBOL_GPL(vfs_kern_mount);
 
 struct vfsmount *
-vfs_submount_sc(const struct dentry *mountpoint, struct sb_config *sc)
-{
-	/* Until it is worked out how to pass the user namespace
-	 * through from the parent mount to the submount don't support
-	 * unprivileged mounts with submounts.
-	 */
-	if (mountpoint->d_sb->s_user_ns != &init_user_ns)
-		return ERR_PTR(-EPERM);
-
-	sc->ms_flags = MS_SUBMOUNT;
-	return vfs_kern_mount_sc(sc);
-}
-EXPORT_SYMBOL_GPL(vfs_submount_sc);
-
-struct vfsmount *
 vfs_submount(const struct dentry *mountpoint, struct file_system_type *type,
 	     const char *name, void *data)
 {
@@ -3247,7 +3249,6 @@ SYSCALL_DEFINE5(fsmount, int, fs_fd, int, dfd, const char __user *, dir_name,
 		unsigned int, at_flags, unsigned int, flags)
 {
 	struct sb_config *sc;
-	struct inode *inode;
 	struct path mountpoint;
 	struct fd f;
 	unsigned int lookup_flags, mnt_flags = 0;
@@ -3316,13 +3317,8 @@ SYSCALL_DEFINE5(fsmount, int, fs_fd, int, dfd, const char __user *, dir_name,
 		goto err_fsfd;
 	}
 
-	ret = security_sb_mountpoint(sc, &mountpoint);
-	if (ret < 0)
-		goto err_mp;
-
 	ret = do_new_mount_sc(sc, &mountpoint, mnt_flags);
 
-err_mp:
 	path_put(&mountpoint);
 err_fsfd:
 	fdput(f);
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 7acca9f53bcd..7dd98709d247 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -130,6 +130,7 @@ struct nfs_sb_config {
 
 	struct nfs_fh		*mntfh;
 	struct nfs_subversion	*nfs_mod;
+	struct nfs_server	*server;
 
 	int (*set_security)(struct super_block *, struct nfs_sb_config *);
 
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index 4ab3338b2208..27ac3b373168 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -1401,7 +1401,6 @@ static int nfs_mount_init_from_sb(struct sb_config *sc,
 {
 	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
 	struct nfs_server *nfss = sb->s_fs_info;
-	struct net *net = nfss->nfs_client->cl_net;
 
 	cfg->flags		= nfss->flags;
 	cfg->rsize		= nfss->rsize;
@@ -1421,9 +1420,9 @@ static int nfs_mount_init_from_sb(struct sb_config *sc,
 	memcpy(&cfg->nfs_server.address, &nfss->nfs_client->cl_addr,
 		cfg->nfs_server.addrlen);
 
-	if (cfg->sc.net_ns != net) {
-		put_net(cfg->sc.net_ns);
-		cfg->sc.net_ns = get_net(net);
+	if (cfg->sc.net_ns != nfss->nfs_client->cl_net) {
+		WARN_ON(1);
+		return -EINVAL;
 	}
 
 	cfg->nfs_mod = nfss->nfs_client->cl_nfs_mod;
@@ -1476,10 +1475,10 @@ static int nfs_init_sb_config(struct sb_config *sc, struct super_block *src_sb)
 struct file_system_type nfs_fs_type = {
 	.owner		= THIS_MODULE,
 	.name		= "nfs",
-	.init_sb_config	= nfs_init_sb_config,
 	.sb_config_size	= sizeof(struct nfs_sb_config),
+	.init_sb_config	= nfs_init_sb_config,
 	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA|FS_IS_NETFS,
 };
 MODULE_ALIAS_FS("nfs");
 EXPORT_SYMBOL_GPL(nfs_fs_type);
@@ -1491,7 +1490,7 @@ struct file_system_type nfs4_fs_type = {
 	.sb_config_size	= sizeof(struct nfs_sb_config),
 	.init_sb_config	= nfs_init_sb_config,
 	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA|FS_IS_NETFS,
 };
 MODULE_ALIAS_FS("nfs4");
 MODULE_ALIAS("nfs4");
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index e95e669e4db8..e8e620b2de41 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -210,15 +210,6 @@ void nfs_release_automount_timer(void)
 		cancel_delayed_work(&nfs_automount_task);
 }
 
-/*
- * Clone a mountpoint of the appropriate type
- */
-static struct vfsmount *nfs_do_clone_mount(struct nfs_server *server,
-					   struct nfs_sb_config *cfg)
-{
-	return vfs_submount_sc(cfg->clone_data.dentry, &cfg->sc);
-}
-
 /**
  * nfs_do_submount - set up mountpoint when crossing a filesystem boundary
  * @dentry - parent directory
@@ -239,8 +230,8 @@ struct vfsmount *nfs_do_submount(struct dentry *dentry, struct nfs_fh *fh,
 	/* Open a new mount context, transferring parameters from the parent
 	 * superblock, including the network namespace.
 	 */
-	sc = __vfs_new_sb_config(&nfs_fs_type, dentry->d_sb, 0,
-				 SB_CONFIG_FOR_SUBMOUNT);
+	sc = vfs_new_sb_config(&nfs_fs_type, dentry->d_sb, 0,
+			       SB_CONFIG_FOR_SUBMOUNT);
 	if (IS_ERR(sc))
 		return ERR_CAST(sc);
 	cfg = container_of(sc, struct nfs_sb_config, sc);
@@ -275,7 +266,7 @@ struct vfsmount *nfs_do_submount(struct dentry *dentry, struct nfs_fh *fh,
 	if (ret < 0)
 		goto err_sc;
 
-	mnt = nfs_do_clone_mount(NFS_SB(dentry->d_sb), cfg);
+	mnt = vfs_kern_mount_sc(&cfg->sc);
 	goto err_sc;
 
 err_buffer:
diff --git a/fs/nfs/nfs4namespace.c b/fs/nfs/nfs4namespace.c
index 60b711aa0618..978ee33a19ab 100644
--- a/fs/nfs/nfs4namespace.c
+++ b/fs/nfs/nfs4namespace.c
@@ -322,7 +322,7 @@ static struct vfsmount *try_location(struct dentry *dentry,
 		p += cfg->nfs_server.export_path_len;
 		*p = 0;
 
-		mnt = vfs_submount_sc(cfg->clone_data.dentry, &cfg->sc);
+		mnt = vfs_kern_mount_sc(&cfg->sc);
 		if (!IS_ERR(mnt))
 			break;
 	}
@@ -347,8 +347,8 @@ static struct vfsmount *nfs_follow_referral(struct dentry *dentry,
 	if (locations == NULL || locations->nlocations <= 0)
 		goto out;
 
-	sc = __vfs_new_sb_config(&nfs4_fs_type, dentry->d_sb, 0,
-				 SB_CONFIG_FOR_SUBMOUNT);
+	sc = vfs_new_sb_config(&nfs4_fs_type, dentry->d_sb, 0,
+			       SB_CONFIG_FOR_SUBMOUNT);
 	if (IS_ERR(sc)) {
 		mnt = ERR_CAST(sc);
 		goto out;
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 34a7c16cb33c..92293315b070 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -1013,13 +1013,14 @@ static void nfs_clone_super(struct super_block *sb, struct nfs_sb_config *cfg)
  	nfs_initialise_sb(sb);
 }
 
-static int nfs_compare_mount_options(const struct super_block *s, const struct nfs_server *b, int flags)
+static int nfs_compare_mount_options(const struct super_block *s, const struct nfs_server *b,
+				     const struct nfs_sb_config *cfg)
 {
 	const struct nfs_server *a = s->s_fs_info;
 	const struct rpc_clnt *clnt_a = a->client;
 	const struct rpc_clnt *clnt_b = b->client;
 
-	if ((s->s_flags & NFS_MS_MASK) != (flags & NFS_MS_MASK))
+	if ((s->s_flags & NFS_MS_MASK) != (cfg->sc.ms_flags & NFS_MS_MASK))
 		goto Ebusy;
 	if (a->nfs_client != b->nfs_client)
 		goto Ebusy;
@@ -1045,18 +1046,13 @@ static int nfs_compare_mount_options(const struct super_block *s, const struct n
 	return 0;
 }
 
-struct nfs_sb_mountdata {
-	struct nfs_server *server;
-	int mntflags;
-};
-
-static int nfs_set_super(struct super_block *s, void *data)
+static int nfs_set_super(struct super_block *s, struct sb_config *sc)
 {
-	struct nfs_sb_mountdata *sb_mntdata = data;
-	struct nfs_server *server = sb_mntdata->server;
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+	struct nfs_server *server = cfg->server;
 	int ret;
 
-	s->s_flags = sb_mntdata->mntflags;
+	s->s_flags = cfg->sc.ms_flags;
 	s->s_fs_info = server;
 	s->s_d_op = server->nfs_client->rpc_ops->dentry_ops;
 	ret = set_anon_super(s, server);
@@ -1069,11 +1065,6 @@ static int nfs_compare_super_address(struct nfs_server *server1,
 				     struct nfs_server *server2)
 {
 	struct sockaddr *sap1, *sap2;
-	struct rpc_xprt *xprt1 = server1->client->cl_xprt;
-	struct rpc_xprt *xprt2 = server2->client->cl_xprt;
-
-	if (!net_eq(xprt1->xprt_net, xprt2->xprt_net))
-		return 0;
 
 	sap1 = (struct sockaddr *)&server1->nfs_client->cl_addr;
 	sap2 = (struct sockaddr *)&server2->nfs_client->cl_addr;
@@ -1107,11 +1098,10 @@ static int nfs_compare_super_address(struct nfs_server *server1,
 	return 1;
 }
 
-static int nfs_compare_super(struct super_block *sb, void *data)
+static int nfs_compare_super(struct super_block *sb, struct sb_config *sc)
 {
-	struct nfs_sb_mountdata *sb_mntdata = data;
-	struct nfs_server *server = sb_mntdata->server, *old = NFS_SB(sb);
-	int mntflags = sb_mntdata->mntflags;
+	struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+	struct nfs_server *server = cfg->server, *old = NFS_SB(sb);
 
 	if (!nfs_compare_super_address(old, server))
 		return 0;
@@ -1120,7 +1110,7 @@ static int nfs_compare_super(struct super_block *sb, void *data)
 		return 0;
 	if (memcmp(&old->fsid, &server->fsid, sizeof(old->fsid)) != 0)
 		return 0;
-	return nfs_compare_mount_options(sb, server, mntflags);
+	return nfs_compare_mount_options(sb, server, cfg);
 }
 
 #ifdef CONFIG_NFS_FSCACHE
@@ -1199,11 +1189,7 @@ int nfs_get_tree_common(struct nfs_server *server, struct nfs_sb_config *cfg)
 {
 	struct super_block *s;
 	struct dentry *mntroot = ERR_PTR(-ENOMEM);
-	int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
-	struct nfs_sb_mountdata sb_mntdata = {
-		.mntflags = cfg->sc.ms_flags,
-		.server = server,
-	};
+	int (*compare_super)(struct super_block *, struct sb_config *) = nfs_compare_super;
 	int error;
 
 	if (server->flags & NFS_MOUNT_UNSHARED)
@@ -1211,15 +1197,16 @@ int nfs_get_tree_common(struct nfs_server *server, struct nfs_sb_config *cfg)
 
 	/* -o noac implies -o sync */
 	if (server->flags & NFS_MOUNT_NOAC)
-		sb_mntdata.mntflags |= MS_SYNCHRONOUS;
+		cfg->sc.ms_flags |= MS_SYNCHRONOUS;
 
 	if (cfg->clone_data.cloned && cfg->clone_data.sb != NULL)
 		if (cfg->clone_data.sb->s_flags & MS_SYNCHRONOUS)
-			sb_mntdata.mntflags |= MS_SYNCHRONOUS;
+			cfg->sc.ms_flags |= MS_SYNCHRONOUS;
 
 	/* Get a superblock - note that we may end up sharing one that already exists */
-	s = sget(cfg->nfs_mod->nfs_fs, compare_super, nfs_set_super, cfg->sc.ms_flags,
-		 &sb_mntdata);
+	cfg->server = server;
+	s = sget_sc(&cfg->sc, compare_super, nfs_set_super);
+	cfg->server = NULL;
 	if (IS_ERR(s)) {
 		error = PTR_ERR(s);
 		errorf("NFS: Couldn't get superblock");
diff --git a/fs/proc/root.c b/fs/proc/root.c
index 9878b62e874c..c5d24c5f23cd 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -299,7 +299,7 @@ int pid_ns_prepare_proc(struct pid_namespace *ns)
 	struct vfsmount *mnt;
 	int ret;
 
-	sc = __vfs_new_sb_config(&proc_fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
+	sc = vfs_new_sb_config(&proc_fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
 	if (IS_ERR(sc))
 		return PTR_ERR(sc);
 
diff --git a/fs/sb_config.c b/fs/sb_config.c
index 4d9bfb982d41..74319550e4e7 100644
--- a/fs/sb_config.c
+++ b/fs/sb_config.c
@@ -178,10 +178,10 @@ int generic_monolithic_mount_data(struct sb_config *ctx, void *data)
 EXPORT_SYMBOL(generic_monolithic_mount_data);
 
 /**
- * __vfs_new_sb_config - Create a superblock config.
+ * vfs_new_sb_config - Create a superblock config.
  * @fs_type: The filesystem type.
  * @src_sb: A superblock from which this one derives (or NULL)
- * @ms_flags: Superblock flags and op flags (such as MS_REMOUNT)
+ * @ms_flags: Superblock flags.
  * @purpose: The purpose that this configuration shall be used for.
  *
  * Open a filesystem and create a mount context.  The mount context is
@@ -189,10 +189,10 @@ EXPORT_SYMBOL(generic_monolithic_mount_data);
  * another superblock (@src_sb), may have parameters such as namespaces copied
  * across from that superblock.
  */
-struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
-				      struct super_block *src_sb,
-				      unsigned int ms_flags,
-				      enum sb_config_purpose purpose)
+struct sb_config *vfs_new_sb_config(struct file_system_type *fs_type,
+				    struct super_block *src_sb,
+				    unsigned int ms_flags,
+				    enum sb_config_purpose purpose)
 {
 	struct sb_config *sc;
 	size_t sc_size = fs_type->sb_config_size;
@@ -210,10 +210,27 @@ struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
 	sc->purpose	= purpose;
 	sc->ms_flags	= ms_flags;
 	sc->fs_type	= get_filesystem(fs_type);
-	sc->net_ns	= get_net(current->nsproxy->net_ns);
-	sc->user_ns	= get_user_ns(current_user_ns());
 	sc->cred	= get_current_cred();
 
+	switch (purpose) {
+	case SB_CONFIG_FOR_NEW:
+		sc->user_ns = get_user_ns(sc->cred->user_ns);
+		if (fs_type->fs_flags & FS_IS_NETFS)
+			sc->net_ns = get_net(current->nsproxy->net_ns);
+		break;
+	case SB_CONFIG_FOR_SUBMOUNT:
+		sc->user_ns = get_user_ns(src_sb->s_user_ns);
+		if (src_sb->s_net_ns)
+			sc->net_ns = get_net(src_sb->s_net_ns);
+		break;
+	case SB_CONFIG_FOR_REMOUNT:
+		/* We don't pin any namespaces as the superblock's
+		 * subscriptions cannot be changed at this point.
+		 */
+		break;
+	}
+
+
 	/* TODO: Make all filesystems support this unconditionally */
 	if (sc->fs_type->init_sb_config) {
 		ret = sc->fs_type->init_sb_config(sc, src_sb);
@@ -236,31 +253,6 @@ struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
 	put_sb_config(sc);
 	return ERR_PTR(ret);
 }
-EXPORT_SYMBOL(__vfs_new_sb_config);
-
-/**
- * vfs_new_sb_config - Create a superblock config for a new mount.
- * @fs_name: The name of the filesystem
- *
- * Open a filesystem and create a superblock config context for a new mount
- * that will hold the mount options, device name, security details, etc..  Note
- * that the caller should check the ->ops pointer in the returned context to
- * determine whether the filesystem actually supports the superblock context
- * itself.
- */
-struct sb_config *vfs_new_sb_config(const char *fs_name)
-{
-	struct file_system_type *fs_type;
-	struct sb_config *sc;
-
-	fs_type = get_fs_type(fs_name);
-	if (!fs_type)
-		return ERR_PTR(-ENODEV);
-
-	sc = __vfs_new_sb_config(fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
-	put_filesystem(fs_type);
-	return sc;
-}
 EXPORT_SYMBOL(vfs_new_sb_config);
 
 /**
@@ -274,8 +266,8 @@ EXPORT_SYMBOL(vfs_new_sb_config);
 struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
 				  unsigned int ms_flags)
 {
-	return __vfs_new_sb_config(mnt->mnt_sb->s_type, mnt->mnt_sb,
-				   ms_flags, SB_CONFIG_FOR_REMOUNT);
+	return vfs_new_sb_config(mnt->mnt_sb->s_type, mnt->mnt_sb,
+				 ms_flags, SB_CONFIG_FOR_REMOUNT);
 }
 
 /**
diff --git a/fs/super.c b/fs/super.c
index c2c0435550f6..1412ac0a88d8 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -34,6 +34,7 @@
 #include <linux/fsnotify.h>
 #include <linux/lockdep.h>
 #include <linux/user_namespace.h>
+#include <net/net_namespace.h>
 #include <linux/sb_config.h>
 #include "internal.h"
 
@@ -174,16 +175,13 @@ static void destroy_super(struct super_block *s)
 }
 
 /**
- *	alloc_super	-	create new superblock
- *	@type:	filesystem type superblock should belong to
- *	@flags: the mount flags
- *	@user_ns: User namespace for the super_block
+ *	alloc_super - Create new superblock
+ *	@sc: The filesystem configuration context
  *
  *	Allocates and initializes a new &struct super_block.  alloc_super()
  *	returns a pointer new superblock or %NULL if allocation had failed.
  */
-static struct super_block *alloc_super(struct file_system_type *type, int flags,
-				       struct user_namespace *user_ns)
+static struct super_block *alloc_super(struct sb_config *sc)
 {
 	struct super_block *s = kzalloc(sizeof(struct super_block),  GFP_USER);
 	static const struct super_operations default_op;
@@ -193,7 +191,8 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
 		return NULL;
 
 	INIT_LIST_HEAD(&s->s_mounts);
-	s->s_user_ns = get_user_ns(user_ns);
+	s->s_user_ns = get_user_ns(sc->user_ns);
+	s->s_net_ns = sc->net_ns ? get_net(sc->net_ns) : NULL;
 
 	if (security_sb_alloc(s))
 		goto fail;
@@ -201,12 +200,12 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
 	for (i = 0; i < SB_FREEZE_LEVELS; i++) {
 		if (__percpu_init_rwsem(&s->s_writers.rw_sem[i],
 					sb_writers_name[i],
-					&type->s_writers_key[i]))
+					&sc->fs_type->s_writers_key[i]))
 			goto fail;
 	}
 	init_waitqueue_head(&s->s_writers.wait_unfrozen);
 	s->s_bdi = &noop_backing_dev_info;
-	s->s_flags = flags;
+	s->s_flags = sc->ms_flags;
 	if (s->s_user_ns != &init_user_ns)
 		s->s_iflags |= SB_I_NODEV;
 	INIT_HLIST_NODE(&s->s_instances);
@@ -223,7 +222,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
 		goto fail;
 
 	init_rwsem(&s->s_umount);
-	lockdep_set_class(&s->s_umount, &type->s_umount_key);
+	lockdep_set_class(&s->s_umount, &sc->fs_type->s_umount_key);
 	/*
 	 * sget() can have s_umount recursion.
 	 *
@@ -243,7 +242,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
 	s->s_count = 1;
 	atomic_set(&s->s_active, 1);
 	mutex_init(&s->s_vfs_rename_mutex);
-	lockdep_set_class(&s->s_vfs_rename_mutex, &type->s_vfs_rename_key);
+	lockdep_set_class(&s->s_vfs_rename_mutex, &sc->fs_type->s_vfs_rename_key);
 	mutex_init(&s->s_dquot.dqio_mutex);
 	s->s_maxbytes = MAX_NON_LFS;
 	s->s_op = &default_op;
@@ -456,6 +455,80 @@ void generic_shutdown_super(struct super_block *sb)
 EXPORT_SYMBOL(generic_shutdown_super);
 
 /**
+ *	sget_sc - Find or create a superblock
+ *	@sc:	Configuration context.
+ *	@test:	comparison callback
+ *	@set:	setup callback
+ */
+struct super_block *sget_sc(struct sb_config *sc,
+			    int (*test)(struct super_block *, struct sb_config *),
+			    int (*set)(struct super_block *, struct sb_config *))
+{
+	struct super_block *s = NULL;
+	struct super_block *old;
+	int err;
+
+	if (!(sc->ms_flags & MS_KERNMOUNT) &&
+	    sc->purpose != SB_CONFIG_FOR_SUBMOUNT) {
+		if (!(sc->fs_type->fs_flags & FS_USERNS_MOUNT) &&
+		    !capable(CAP_SYS_ADMIN))
+			return ERR_PTR(-EPERM);
+		else if (!ns_capable(sc->user_ns, CAP_SYS_ADMIN))
+			return ERR_PTR(-EPERM);
+	}
+
+retry:
+	spin_lock(&sb_lock);
+	if (test) {
+		hlist_for_each_entry(old, &sc->fs_type->fs_supers, s_instances) {
+			if (!test(old, sc))
+				continue;
+			if (sc->user_ns != old->s_user_ns &&
+			    !net_eq(sc->net_ns, old->s_net_ns)) {
+				spin_unlock(&sb_lock);
+				if (s) {
+					up_write(&s->s_umount);
+					destroy_super(s);
+				}
+				return ERR_PTR(-EBUSY);
+			}
+			if (!grab_super(old))
+				goto retry;
+			if (s) {
+				up_write(&s->s_umount);
+				destroy_super(s);
+				s = NULL;
+			}
+			return old;
+		}
+	}
+	if (!s) {
+		spin_unlock(&sb_lock);
+		s = alloc_super(sc);
+		if (!s)
+			return ERR_PTR(-ENOMEM);
+		goto retry;
+	}
+
+	err = set(s, sc);
+	if (err) {
+		spin_unlock(&sb_lock);
+		up_write(&s->s_umount);
+		destroy_super(s);
+		return ERR_PTR(err);
+	}
+	s->s_type = sc->fs_type;
+	strlcpy(s->s_id, s->s_type->name, sizeof(s->s_id));
+	list_add_tail(&s->s_list, &super_blocks);
+	hlist_add_head(&s->s_instances, &s->s_type->fs_supers);
+	spin_unlock(&sb_lock);
+	get_filesystem(s->s_type);
+	register_shrinker(&s->s_shrink);
+	return s;
+}
+EXPORT_SYMBOL(sget_sc);
+
+/**
  *	sget_userns -	find or create a superblock
  *	@type:	filesystem type superblock should belong to
  *	@test:	comparison callback
@@ -504,7 +577,14 @@ struct super_block *sget_userns(struct file_system_type *type,
 	}
 	if (!s) {
 		spin_unlock(&sb_lock);
-		s = alloc_super(type, (flags & ~MS_SUBMOUNT), user_ns);
+		{
+			struct sb_config sc = {
+				.fs_type	= type,
+				.ms_flags	= flags & ~MS_SUBMOUNT,
+				.user_ns	= user_ns,
+			};
+			s = alloc_super(&sc);
+		}
 		if (!s)
 			return ERR_PTR(-ENOMEM);
 		goto retry;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 1acb76f400c4..110aa4125787 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1388,6 +1388,11 @@ struct super_block {
 	 */
 	struct user_namespace *s_user_ns;
 
+	/* If a network filesystem, this is the network namespace in which it
+	 * resides.
+	 */
+	struct net *s_net_ns;
+	
 	/*
 	 * Keep the lru lists last in the structure so they always sit on their
 	 * own individual cachelines.
@@ -2022,11 +2027,12 @@ int sync_inode_metadata(struct inode *inode, int wait);
 
 struct file_system_type {
 	const char *name;
-	int fs_flags;
+	unsigned int fs_flags;
 #define FS_REQUIRES_DEV		1 
 #define FS_BINARY_MOUNTDATA	2
 #define FS_HAS_SUBTYPE		4
 #define FS_USERNS_MOUNT		8	/* Can be mounted by userns root */
+#define FS_IS_NETFS		0x10	/* Network fs that uses net namespace (->s_net_ns) */
 #define FS_RENAME_DOES_D_MOVE	32768	/* FS will handle d_move() during rename() internally. */
 	unsigned short sb_config_size;	/* Size of superblock config context to allocate */
 	struct dentry *(*mount) (struct file_system_type *, int,
@@ -2075,6 +2081,9 @@ void deactivate_locked_super(struct super_block *sb);
 int set_anon_super(struct super_block *s, void *data);
 int get_anon_bdev(dev_t *);
 void free_anon_bdev(dev_t);
+struct super_block *sget_sc(struct sb_config *sc,
+			    int (*test)(struct super_block *, struct sb_config *),
+			    int (*set)(struct super_block *, struct sb_config *));
 struct super_block *sget_userns(struct file_system_type *type,
 			int (*test)(struct super_block *,void *),
 			int (*set)(struct super_block *,void *),
diff --git a/include/linux/mount.h b/include/linux/mount.h
index a5dca6abc4d5..e57067da7c2a 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -95,8 +95,6 @@ extern struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc);
 extern struct vfsmount *vfs_submount(const struct dentry *mountpoint,
 				     struct file_system_type *type,
 				     const char *name, void *data);
-extern struct vfsmount *vfs_submount_sc(const struct dentry *mountpoint,
-					struct sb_config *sc);
 
 extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list);
 extern void mark_mounts_for_expiry(struct list_head *mounts);
diff --git a/include/linux/sb_config.h b/include/linux/sb_config.h
index 144258d82fa1..eec37099f388 100644
--- a/include/linux/sb_config.h
+++ b/include/linux/sb_config.h
@@ -69,11 +69,10 @@ struct sb_config_operations {
 	int (*get_tree)(struct sb_config *sc);
 };
 
-extern struct sb_config *vfs_new_sb_config(const char *fs_name);
-extern struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
-					     struct super_block *src_sb,
-					     unsigned int ms_flags,
-					     enum sb_config_purpose purpose);
+extern struct sb_config *vfs_new_sb_config(struct file_system_type *fs_type,
+					   struct super_block *src_sb,
+					   unsigned int ms_flags,
+					   enum sb_config_purpose purpose);
 extern struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
 					 unsigned int ms_flags);
 extern struct sb_config *vfs_dup_sb_config(struct sb_config *src);

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* Some filesystems set MNT_* flags in superblock->s_flags
  2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
                   ` (25 preceding siblings ...)
  2017-06-02 10:14 ` David Howells
@ 2017-06-09  7:48 ` David Howells
  2017-06-09  8:02   ` Miklos Szeredi
  26 siblings, 1 reply; 30+ messages in thread
From: David Howells @ 2017-06-09  7:48 UTC (permalink / raw)
  To: Al Viro
  Cc: dhowells, mszeredi, jlayton, linux-fsdevel, linux-nfs, linux-kernel

Al Viro <viro@ZenIV.linux.org.uk> wrote:

> 	* what the hell is ms_flags thing doing in __vfs_new_sb_config()?
> It's a really vile mix of unrelated flags and operations we had in existing
> mount(2) ABI.  With MS_KERNMOUNT thrown into that loo^Wmix.  Sure, we need
> to parse the garbage fed to mount(2).  And we need to pass that garbage to
> "legacy" types as well, but let's not inflict it upon the new mechanisms.

Hmmm...  Some ->remount_fs() operations attempt to alter the MS_* flags that
correspond to MNT_* flags.  Coda, for example:

	static int coda_remount(struct super_block *sb, int *flags, char *data)
	{
		sync_filesystem(sb);
		*flags |= MS_NOATIME;
		return 0;
	}

But this is quashed in do_remount_sb:

	sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK);

And others set them directly in s_flags, v9fs_fill_super() for example sets
MS_NOATIME:

	sb->s_flags |= MS_ACTIVE | MS_DIRSYNC | MS_NOATIME;

I'm guessing things like this should be got rid of, but does there need to be
a way to inform mount() that these should be set on the vfsmount?

David

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: Some filesystems set MNT_* flags in superblock->s_flags
  2017-06-09  7:48 ` Some filesystems set MNT_* flags in superblock->s_flags David Howells
@ 2017-06-09  8:02   ` Miklos Szeredi
  0 siblings, 0 replies; 30+ messages in thread
From: Miklos Szeredi @ 2017-06-09  8:02 UTC (permalink / raw)
  To: David Howells; +Cc: Al Viro, Jeff Layton, linux-fsdevel, linux-nfs, lkml

On Fri, Jun 9, 2017 at 9:48 AM, David Howells <dhowells@redhat.com> wrote:
> Al Viro <viro@ZenIV.linux.org.uk> wrote:
>
>>       * what the hell is ms_flags thing doing in __vfs_new_sb_config()?
>> It's a really vile mix of unrelated flags and operations we had in existing
>> mount(2) ABI.  With MS_KERNMOUNT thrown into that loo^Wmix.  Sure, we need
>> to parse the garbage fed to mount(2).  And we need to pass that garbage to
>> "legacy" types as well, but let's not inflict it upon the new mechanisms.
>
> Hmmm...  Some ->remount_fs() operations attempt to alter the MS_* flags that
> correspond to MNT_* flags.  Coda, for example:
>
>         static int coda_remount(struct super_block *sb, int *flags, char *data)
>         {
>                 sync_filesystem(sb);
>                 *flags |= MS_NOATIME;
>                 return 0;
>         }
>
> But this is quashed in do_remount_sb:
>
>         sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK);
>
> And others set them directly in s_flags, v9fs_fill_super() for example sets
> MS_NOATIME:
>
>         sb->s_flags |= MS_ACTIVE | MS_DIRSYNC | MS_NOATIME;
>
> I'm guessing things like this should be got rid of, but does there need to be
> a way to inform mount() that these should be set on the vfsmount?

Looks like MS_NOATIME is used in two *completely* separate roles:

  - passed to mount(2) to set MNT_NOATIME
  - set on sb->s_flags by filesystem to prevent atime modification

Btw. the second role can also be achieved by setting S_NOATIME on
inode, so this role of MS_NOATIME looks like being superfluous.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 30+ messages in thread

end of thread, other threads:[~2017-06-09  8:02 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-05-22 15:50 [RFC][PATCH 00/23] VFS: Introduce superblock configuration context [ver #4] David Howells
2017-05-22 15:51 ` [PATCH 01/23] Provide a function to create a NUL-terminated string from unterminated data " David Howells
2017-05-22 15:51 ` [PATCH 02/23] VFS: Clean up whitespace in fs/namespace.c " David Howells
2017-05-22 15:51 ` [PATCH 03/23] VFS: Make get_mnt_ns() return the namespace " David Howells
2017-05-22 15:52 ` [PATCH 04/23] VFS: Make get_filesystem() return the affected filesystem " David Howells
2017-05-22 15:52 ` [PATCH 05/23] VFS: Provide empty name qstr " David Howells
2017-05-22 15:52 ` [PATCH 06/23] Provide supplementary error message facility " David Howells
2017-05-22 15:52 ` [PATCH 07/23] VFS: Introduce the structs and doc for a superblock configuration context " David Howells
2017-05-22 15:52 ` [PATCH 08/23] VFS: Add LSM hooks for " David Howells
2017-05-22 15:53 ` [PATCH 09/23] VFS: Implement a " David Howells
2017-05-22 15:53 ` [PATCH 10/23] VFS: Remove unused code after superblock config context changes " David Howells
2017-05-22 15:53 ` [PATCH 11/23] VFS: Implement fsopen() to prepare for a mount " David Howells
2017-05-22 15:53 ` [PATCH 12/23] VFS: Implement fsmount() to effect a pre-configured " David Howells
2017-05-22 15:53 ` [PATCH 13/23] VFS: Add a sample program for fsopen/fsmount " David Howells
2017-05-22 15:53 ` [PATCH 14/23] procfs: Move proc_fill_super() to fs/proc/root.c " David Howells
2017-05-22 15:53 ` [PATCH 15/23] proc: Add superblock config support to procfs " David Howells
2017-05-22 15:53 ` [PATCH 16/23] NFS: Move sb-configuration bits into their own file " David Howells
2017-05-22 15:54 ` [PATCH 17/23] NFS: Constify mount argument match tables " David Howells
2017-05-22 15:54 ` [PATCH 18/23] NFS: Rename struct nfs_parsed_mount_data to struct nfs_sb_config " David Howells
2017-05-22 15:54 ` [PATCH 19/23] NFS: Split nfs_parse_mount_options() " David Howells
2017-05-22 15:54 ` [PATCH 20/23] NFS: Deindent nfs_sb_config_parse_option() " David Howells
2017-05-22 15:54 ` [PATCH 21/23] NFS: Add a small buffer in nfs_sb_config to avoid string dup " David Howells
2017-05-22 15:54 ` [PATCH 22/23] NFS: Do some tidying of the parsing code " David Howells
2017-05-22 15:54 ` [PATCH 23/23] NFS: Add sb_config support. " David Howells
2017-05-30 14:50 ` [RFC][PATCH 00/23] VFS: Introduce superblock configuration context " Al Viro
2017-05-30 15:36 ` David Howells
2017-05-31  7:51   ` Miklos Szeredi
2017-06-02 10:14 ` David Howells
2017-06-09  7:48 ` Some filesystems set MNT_* flags in superblock->s_flags David Howells
2017-06-09  8:02   ` Miklos Szeredi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).