diff options
Diffstat (limited to 'fs/nfs/super.c')
| -rw-r--r-- | fs/nfs/super.c | 2163 | 
1 files changed, 943 insertions, 1220 deletions
diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 0a42e8f4adc..084af1060d7 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -31,6 +31,7 @@  #include <linux/errno.h>  #include <linux/unistd.h>  #include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/addr.h>  #include <linux/sunrpc/stats.h>  #include <linux/sunrpc/metrics.h>  #include <linux/sunrpc/xprtsock.h> @@ -39,10 +40,8 @@  #include <linux/nfs_mount.h>  #include <linux/nfs4_mount.h>  #include <linux/lockd/bind.h> -#include <linux/smp_lock.h>  #include <linux/seq_file.h>  #include <linux/mount.h> -#include <linux/mnt_namespace.h>  #include <linux/namei.h>  #include <linux/nfs_idmap.h>  #include <linux/vfs.h> @@ -54,8 +53,9 @@  #include <linux/nfs_xdr.h>  #include <linux/magic.h>  #include <linux/parser.h> +#include <linux/nsproxy.h> +#include <linux/rcupdate.h> -#include <asm/system.h>  #include <asm/uaccess.h>  #include "nfs4_fs.h" @@ -64,8 +64,18 @@  #include "iostat.h"  #include "internal.h"  #include "fscache.h" +#include "nfs4session.h" +#include "pnfs.h" +#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 */ @@ -74,13 +84,13 @@ enum {  	Opt_cto, Opt_nocto,  	Opt_ac, Opt_noac,  	Opt_lock, Opt_nolock, -	Opt_v2, Opt_v3, Opt_v4,  	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, @@ -92,10 +102,10 @@ enum {  	Opt_namelen,  	Opt_mountport,  	Opt_mountvers, -	Opt_nfsvers,  	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, @@ -127,9 +137,6 @@ static const match_table_t nfs_mount_option_tokens = {  	{ Opt_noac, "noac" },  	{ Opt_lock, "lock" },  	{ Opt_nolock, "nolock" }, -	{ Opt_v2, "v2" }, -	{ Opt_v3, "v3" }, -	{ Opt_v4, "v4" },  	{ Opt_udp, "udp" },  	{ Opt_tcp, "tcp" },  	{ Opt_rdma, "rdma" }, @@ -143,6 +150,8 @@ static const match_table_t nfs_mount_option_tokens = {  	{ Opt_noresvport, "noresvport" },  	{ Opt_fscache, "fsc" },  	{ Opt_nofscache, "nofsc" }, +	{ Opt_migration, "migration" }, +	{ Opt_nomigration, "nomigration" },  	{ Opt_port, "port=%s" },  	{ Opt_rsize, "rsize=%s" }, @@ -158,9 +167,10 @@ static const match_table_t nfs_mount_option_tokens = {  	{ 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_minorversion, "minorversion=%s" },  	{ Opt_sec, "sec=%s" },  	{ Opt_proto, "proto=%s" }, @@ -174,6 +184,9 @@ static const match_table_t nfs_mount_option_tokens = {  	{ 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 }  }; @@ -254,120 +267,101 @@ static match_table_t nfs_local_lock_tokens = {  	{ 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 void nfs_umount_begin(struct super_block *); -static int  nfs_statfs(struct dentry *, struct kstatfs *); -static int  nfs_show_options(struct seq_file *, struct vfsmount *); -static int  nfs_show_stats(struct seq_file *, struct vfsmount *); -static int nfs_get_sb(struct file_system_type *, int, const char *, void *, struct vfsmount *);  static struct dentry *nfs_xdev_mount(struct file_system_type *fs_type,  		int flags, const char *dev_name, void *raw_data); -static void nfs_put_super(struct super_block *); -static void nfs_kill_super(struct super_block *); -static int nfs_remount(struct super_block *sb, int *flags, char *raw_data); -static struct file_system_type nfs_fs_type = { +struct file_system_type nfs_fs_type = {  	.owner		= THIS_MODULE,  	.name		= "nfs", -	.get_sb		= nfs_get_sb, +	.mount		= nfs_fs_mount,  	.kill_sb	= nfs_kill_super, -	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA, +	.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_REVAL_DOT|FS_BINARY_MOUNTDATA, +	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,  }; -static const struct super_operations nfs_sops = { +const struct super_operations nfs_sops = {  	.alloc_inode	= nfs_alloc_inode,  	.destroy_inode	= nfs_destroy_inode,  	.write_inode	= nfs_write_inode, +	.drop_inode	= nfs_drop_inode,  	.put_super	= nfs_put_super,  	.statfs		= nfs_statfs,  	.evict_inode	= nfs_evict_inode,  	.umount_begin	= nfs_umount_begin,  	.show_options	= nfs_show_options, +	.show_devname	= nfs_show_devname, +	.show_path	= nfs_show_path,  	.show_stats	= nfs_show_stats,  	.remount_fs	= nfs_remount,  }; +EXPORT_SYMBOL_GPL(nfs_sops); -#ifdef CONFIG_NFS_V4 -static int nfs4_validate_text_mount_data(void *options, +#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); -static int nfs4_try_mount(int flags, const char *dev_name, -	struct nfs_parsed_mount_data *data, struct vfsmount *mnt); -static int nfs4_get_sb(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt); -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_xdev_mount(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *raw_data); -static int nfs4_referral_get_sb(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt); -static struct dentry *nfs4_remote_referral_mount(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *raw_data); -static void nfs4_kill_super(struct super_block *sb); - -static struct file_system_type nfs4_fs_type = { -	.owner		= THIS_MODULE, -	.name		= "nfs4", -	.get_sb		= nfs4_get_sb, -	.kill_sb	= nfs4_kill_super, -	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA, -}; - -static struct file_system_type nfs4_remote_fs_type = { -	.owner		= THIS_MODULE, -	.name		= "nfs4", -	.mount		= nfs4_remote_mount, -	.kill_sb	= nfs4_kill_super, -	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA, -}; -struct file_system_type nfs4_xdev_fs_type = { +struct file_system_type nfs4_fs_type = {  	.owner		= THIS_MODULE,  	.name		= "nfs4", -	.mount		= nfs4_xdev_mount, -	.kill_sb	= nfs4_kill_super, -	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA, +	.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 struct file_system_type nfs4_remote_referral_fs_type = { -	.owner		= THIS_MODULE, -	.name		= "nfs4", -	.mount		= nfs4_remote_referral_mount, -	.kill_sb	= nfs4_kill_super, -	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA, -}; +static int __init register_nfs4_fs(void) +{ +	return register_filesystem(&nfs4_fs_type); +} -struct file_system_type nfs4_referral_fs_type = { -	.owner		= THIS_MODULE, -	.name		= "nfs4", -	.get_sb		= nfs4_referral_get_sb, -	.kill_sb	= nfs4_kill_super, -	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_REVAL_DOT|FS_BINARY_MOUNTDATA, -}; +static void unregister_nfs4_fs(void) +{ +	unregister_filesystem(&nfs4_fs_type); +} +#else +static int __init register_nfs4_fs(void) +{ +	return 0; +} -static const struct super_operations nfs4_sops = { -	.alloc_inode	= nfs_alloc_inode, -	.destroy_inode	= nfs_destroy_inode, -	.write_inode	= nfs_write_inode, -	.put_super	= nfs_put_super, -	.statfs		= nfs_statfs, -	.evict_inode	= nfs4_evict_inode, -	.umount_begin	= nfs_umount_begin, -	.show_options	= nfs_show_options, -	.show_stats	= nfs_show_stats, -	.remount_fs	= nfs_remount, -}; +static void unregister_nfs4_fs(void) +{ +}  #endif  static struct shrinker acl_shrinker = { -	.shrink		= nfs_access_cache_shrinker, +	.count_objects	= nfs_access_cache_count, +	.scan_objects	= nfs_access_cache_scan,  	.seeks		= DEFAULT_SEEKS,  }; @@ -382,21 +376,18 @@ int __init register_nfs_fs(void)  	if (ret < 0)  		goto error_0; -	ret = nfs_register_sysctl(); +	ret = register_nfs4_fs();  	if (ret < 0)  		goto error_1; -#ifdef CONFIG_NFS_V4 -	ret = register_filesystem(&nfs4_fs_type); + +	ret = nfs_register_sysctl();  	if (ret < 0)  		goto error_2; -#endif  	register_shrinker(&acl_shrinker);  	return 0; -#ifdef CONFIG_NFS_V4  error_2: -	nfs_unregister_sysctl(); -#endif +	unregister_nfs4_fs();  error_1:  	unregister_filesystem(&nfs_fs_type);  error_0: @@ -409,10 +400,8 @@ error_0:  void __exit unregister_nfs_fs(void)  {  	unregister_shrinker(&acl_shrinker); -#ifdef CONFIG_NFS_V4 -	unregister_filesystem(&nfs4_fs_type); -#endif  	nfs_unregister_sysctl(); +	unregister_nfs4_fs();  	unregister_filesystem(&nfs_fs_type);  } @@ -423,6 +412,7 @@ void nfs_sb_active(struct super_block *sb)  	if (atomic_inc_return(&server->active) == 1)  		atomic_inc(&sb->s_active);  } +EXPORT_SYMBOL_GPL(nfs_sb_active);  void nfs_sb_deactive(struct super_block *sb)  { @@ -431,11 +421,12 @@ void nfs_sb_deactive(struct super_block *sb)  	if (atomic_dec_and_test(&server->active))  		deactivate_super(sb);  } +EXPORT_SYMBOL_GPL(nfs_sb_deactive);  /*   * Deliver file system statistics to userspace   */ -static int nfs_statfs(struct dentry *dentry, struct kstatfs *buf) +int nfs_statfs(struct dentry *dentry, struct kstatfs *buf)  {  	struct nfs_server *server = NFS_SB(dentry->d_sb);  	unsigned char blockbits; @@ -496,6 +487,7 @@ static int nfs_statfs(struct dentry *dentry, struct kstatfs *buf)  	dprintk("%s: statfs error = %d\n", __func__, -error);  	return error;  } +EXPORT_SYMBOL_GPL(nfs_statfs);  /*   * Map the security flavour number to a name @@ -505,7 +497,8 @@ static const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour)  	static const struct {  		rpc_authflavor_t flavour;  		const char *str; -	} sec_flavours[] = { +	} sec_flavours[NFS_AUTH_INFO_MAX_FLAVORS] = { +		/* update NFS_AUTH_INFO_MAX_FLAVORS when this list changes! */  		{ RPC_AUTH_NULL, "null" },  		{ RPC_AUTH_UNIX, "sys" },  		{ RPC_AUTH_GSS_KRB5, "krb5" }, @@ -593,20 +586,21 @@ static void nfs_show_mountd_options(struct seq_file *m, struct nfs_server *nfss,  	if (nfss->mountd_version || showdefaults)  		seq_printf(m, ",mountvers=%u", nfss->mountd_version); -	if (nfss->mountd_port || showdefaults) +	if ((nfss->mountd_port && +		nfss->mountd_port != (unsigned short)NFS_UNSPEC_PORT) || +		showdefaults)  		seq_printf(m, ",mountport=%u", nfss->mountd_port);  	nfs_show_mountd_netid(m, nfss, showdefaults);  } -#ifdef CONFIG_NFS_V4 +#if IS_ENABLED(CONFIG_NFS_V4)  static void nfs_show_nfsv4_options(struct seq_file *m, struct nfs_server *nfss,  				    int showdefaults)  {  	struct nfs_client *clp = nfss->nfs_client;  	seq_printf(m, ",clientaddr=%s", clp->cl_ipaddr); -	seq_printf(m, ",minorversion=%u", clp->cl_minorversion);  }  #else  static void nfs_show_nfsv4_options(struct seq_file *m, struct nfs_server *nfss, @@ -615,6 +609,15 @@ static void nfs_show_nfsv4_options(struct seq_file *m, struct nfs_server *nfss,  }  #endif +static void nfs_show_nfs_version(struct seq_file *m, +		unsigned int version, +		unsigned int minorversion) +{ +	seq_printf(m, ",vers=%u", version); +	if (version == 4) +		seq_printf(m, ".%u", minorversion); +} +  /*   * Describe the mount options in force on this server representation   */ @@ -642,7 +645,7 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,  	u32 version = clp->rpc_ops->version;  	int local_flock, local_fcntl; -	seq_printf(m, ",vers=%u", version); +	nfs_show_nfs_version(m, version, clp->cl_minorversion);  	seq_printf(m, ",rsize=%u", nfss->rsize);  	seq_printf(m, ",wsize=%u", nfss->wsize);  	if (nfss->bsize != 0) @@ -662,8 +665,10 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,  		else  			seq_puts(m, nfs_infop->nostr);  	} +	rcu_read_lock();  	seq_printf(m, ",proto=%s",  		   rpc_peeraddr2str(nfss->client, RPC_DISPLAY_NETID)); +	rcu_read_unlock();  	if (version == 4) {  		if (nfss->port != NFS_PORT)  			seq_printf(m, ",port=%u", nfss->port); @@ -683,6 +688,9 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,  	if (nfss->options & NFS_OPTION_FSCACHE)  		seq_printf(m, ",fsc"); +	if (nfss->options & NFS_OPTION_MIGRATION) +		seq_printf(m, ",migration"); +  	if (nfss->flags & NFS_MOUNT_LOOKUP_CACHE_NONEG) {  		if (nfss->flags & NFS_MOUNT_LOOKUP_CACHE_NONE)  			seq_printf(m, ",lookupcache=none"); @@ -706,26 +714,96 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,  /*   * Describe the mount options on this VFS mountpoint   */ -static int nfs_show_options(struct seq_file *m, struct vfsmount *mnt) +int nfs_show_options(struct seq_file *m, struct dentry *root)  { -	struct nfs_server *nfss = NFS_SB(mnt->mnt_sb); +	struct nfs_server *nfss = NFS_SB(root->d_sb);  	nfs_show_mount_options(m, nfss, 0); +	rcu_read_lock();  	seq_printf(m, ",addr=%s",  			rpc_peeraddr2str(nfss->nfs_client->cl_rpcclient,  							RPC_DISPLAY_ADDR)); +	rcu_read_unlock();  	return 0;  } +EXPORT_SYMBOL_GPL(nfs_show_options); + +#if IS_ENABLED(CONFIG_NFS_V4) +#ifdef CONFIG_NFS_V4_1 +static void show_sessions(struct seq_file *m, struct nfs_server *server) +{ +	if (nfs4_has_session(server->nfs_client)) +		seq_printf(m, ",sessions"); +} +#else +static void show_sessions(struct seq_file *m, struct nfs_server *server) {} +#endif +#endif + +#ifdef CONFIG_NFS_V4_1 +static void show_pnfs(struct seq_file *m, struct nfs_server *server) +{ +	seq_printf(m, ",pnfs="); +	if (server->pnfs_curr_ld) +		seq_printf(m, "%s", server->pnfs_curr_ld->name); +	else +		seq_printf(m, "not configured"); +} + +static void show_implementation_id(struct seq_file *m, struct nfs_server *nfss) +{ +	if (nfss->nfs_client && nfss->nfs_client->cl_implid) { +		struct nfs41_impl_id *impl_id = nfss->nfs_client->cl_implid; +		seq_printf(m, "\n\timpl_id:\tname='%s',domain='%s'," +			   "date='%llu,%u'", +			   impl_id->name, impl_id->domain, +			   impl_id->date.seconds, impl_id->date.nseconds); +	} +} +#else +#if IS_ENABLED(CONFIG_NFS_V4) +static void show_pnfs(struct seq_file *m, struct nfs_server *server) +{ +} +#endif +static void show_implementation_id(struct seq_file *m, struct nfs_server *nfss) +{ +} +#endif + +int nfs_show_devname(struct seq_file *m, struct dentry *root) +{ +	char *page = (char *) __get_free_page(GFP_KERNEL); +	char *devname, *dummy; +	int err = 0; +	if (!page) +		return -ENOMEM; +	devname = nfs_path(&dummy, root, page, PAGE_SIZE, 0); +	if (IS_ERR(devname)) +		err = PTR_ERR(devname); +	else +		seq_escape(m, devname, " \t\n\\"); +	free_page((unsigned long)page); +	return err; +} +EXPORT_SYMBOL_GPL(nfs_show_devname); + +int nfs_show_path(struct seq_file *m, struct dentry *dentry) +{ +	seq_puts(m, "/"); +	return 0; +} +EXPORT_SYMBOL_GPL(nfs_show_path);  /*   * Present statistical information for this VFS mountpoint   */ -static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt) +int nfs_show_stats(struct seq_file *m, struct dentry *root)  {  	int i, cpu; -	struct nfs_server *nfss = NFS_SB(mnt->mnt_sb); +	struct nfs_server *nfss = NFS_SB(root->d_sb);  	struct rpc_auth *auth = nfss->client->cl_auth;  	struct nfs_iostats totals = { }; @@ -735,14 +813,16 @@ static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt)  	 * Display all mount option settings  	 */  	seq_printf(m, "\n\topts:\t"); -	seq_puts(m, mnt->mnt_sb->s_flags & MS_RDONLY ? "ro" : "rw"); -	seq_puts(m, mnt->mnt_sb->s_flags & MS_SYNCHRONOUS ? ",sync" : ""); -	seq_puts(m, mnt->mnt_sb->s_flags & MS_NOATIME ? ",noatime" : ""); -	seq_puts(m, mnt->mnt_sb->s_flags & MS_NODIRATIME ? ",nodiratime" : ""); +	seq_puts(m, root->d_sb->s_flags & MS_RDONLY ? "ro" : "rw"); +	seq_puts(m, root->d_sb->s_flags & MS_SYNCHRONOUS ? ",sync" : ""); +	seq_puts(m, root->d_sb->s_flags & MS_NOATIME ? ",noatime" : ""); +	seq_puts(m, root->d_sb->s_flags & MS_NODIRATIME ? ",nodiratime" : "");  	nfs_show_mount_options(m, nfss, 1);  	seq_printf(m, "\n\tage:\t%lu", (jiffies - nfss->mount_time) / HZ); +	show_implementation_id(m, nfss); +  	seq_printf(m, "\n\tcaps:\t");  	seq_printf(m, "caps=0x%x", nfss->caps);  	seq_printf(m, ",wtmult=%u", nfss->wtmult); @@ -750,12 +830,15 @@ static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt)  	seq_printf(m, ",bsize=%u", nfss->bsize);  	seq_printf(m, ",namlen=%u", nfss->namelen); -#ifdef CONFIG_NFS_V4 +#if IS_ENABLED(CONFIG_NFS_V4)  	if (nfss->nfs_client->rpc_ops->version == 4) {  		seq_printf(m, "\n\tnfsv4:\t");  		seq_printf(m, "bm0=0x%x", nfss->attr_bitmask[0]);  		seq_printf(m, ",bm1=0x%x", nfss->attr_bitmask[1]); +		seq_printf(m, ",bm2=0x%x", nfss->attr_bitmask[2]);  		seq_printf(m, ",acl=0x%x", nfss->acl_bitmask); +		show_sessions(m, nfss); +		show_pnfs(m, nfss);  	}  #endif @@ -806,12 +889,13 @@ static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt)  	return 0;  } +EXPORT_SYMBOL_GPL(nfs_show_stats);  /*   * Begin unmount by attempting to remove all automounted mountpoints we added   * in response to xdev traversals and referrals   */ -static void nfs_umount_begin(struct super_block *sb) +void nfs_umount_begin(struct super_block *sb)  {  	struct nfs_server *server;  	struct rpc_clnt *rpc; @@ -825,8 +909,9 @@ static void nfs_umount_begin(struct super_block *sb)  	if (!IS_ERR(rpc))  		rpc_killall_tasks(rpc);  } +EXPORT_SYMBOL_GPL(nfs_umount_begin); -static struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(unsigned int version) +static struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(void)  {  	struct nfs_parsed_mount_data *data; @@ -839,14 +924,28 @@ static struct nfs_parsed_mount_data *nfs_alloc_parsed_mount_data(unsigned int ve  		data->mount_server.port	= NFS_UNSPEC_PORT;  		data->nfs_server.port	= NFS_UNSPEC_PORT;  		data->nfs_server.protocol = XPRT_TRANSPORT_TCP; -		data->auth_flavors[0]	= RPC_AUTH_UNIX; -		data->auth_flavor_len	= 1; -		data->version		= version; +		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.   * @@ -921,57 +1020,170 @@ static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt)  }  /* + * 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 = (sizeof(auth_info->flavors) / +				       sizeof(auth_info->flavors[0])); + +	/* 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. + */ +bool nfs_auth_info_match(const struct nfs_auth_info *auth_info, +			 rpc_authflavor_t match) +{ +	int i; + +	if (!auth_info->flavor_len) +		return true; + +	for (i = 0; i < auth_info->flavor_len; i++) { +		if (auth_info->flavors[i] == match) +			return true; +	} +	return false; +} +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); -	switch (match_token(value, nfs_secflavor_tokens, args)) { -	case Opt_sec_none: -		mnt->auth_flavors[0] = RPC_AUTH_NULL; -		break; -	case Opt_sec_sys: -		mnt->auth_flavors[0] = RPC_AUTH_UNIX; -		break; -	case Opt_sec_krb5: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_KRB5; -		break; -	case Opt_sec_krb5i: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_KRB5I; -		break; -	case Opt_sec_krb5p: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_KRB5P; -		break; -	case Opt_sec_lkey: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_LKEY; +	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_sec_lkeyi: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_LKEYI; +	case Opt_vers_3: +		mnt->flags |= NFS_MOUNT_VER3; +		mnt->version = 3;  		break; -	case Opt_sec_lkeyp: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_LKEYP; +	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_sec_spkm: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_SPKM; +	case Opt_vers_4_0: +		mnt->version = 4; +		mnt->minorversion = 0;  		break; -	case Opt_sec_spkmi: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_SPKMI; +	case Opt_vers_4_1: +		mnt->version = 4; +		mnt->minorversion = 1;  		break; -	case Opt_sec_spkmp: -		mnt->auth_flavors[0] = RPC_AUTH_GSS_SPKMP; +	case Opt_vers_4_2: +		mnt->version = 4; +		mnt->minorversion = 2;  		break;  	default:  		return 0;  	} - -	mnt->auth_flavor_len = 1;  	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; +} +  /*   * 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 @@ -1056,20 +1268,6 @@ static int nfs_parse_mount_options(char *raw,  			mnt->flags |= (NFS_MOUNT_LOCAL_FLOCK |  				       NFS_MOUNT_LOCAL_FCNTL);  			break; -		case Opt_v2: -			mnt->flags &= ~NFS_MOUNT_VER3; -			mnt->version = 2; -			break; -		case Opt_v3: -			mnt->flags |= NFS_MOUNT_VER3; -			mnt->version = 3; -			break; -#ifdef CONFIG_NFS_V4 -		case Opt_v4: -			mnt->flags &= ~NFS_MOUNT_VER3; -			mnt->version = 4; -			break; -#endif  		case Opt_udp:  			mnt->flags &= ~NFS_MOUNT_TCP;  			mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP; @@ -1117,187 +1315,93 @@ static int nfs_parse_mount_options(char *raw,  			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: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0 || option > USHRT_MAX) +			if (nfs_get_option_ul(args, &option) || +			    option > USHRT_MAX)  				goto out_invalid_value;  			mnt->nfs_server.port = option;  			break;  		case Opt_rsize: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->rsize = option;  			break;  		case Opt_wsize: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->wsize = option;  			break;  		case Opt_bsize: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->bsize = option;  			break;  		case Opt_timeo: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0 || option == 0) +			if (nfs_get_option_ul(args, &option) || option == 0)  				goto out_invalid_value;  			mnt->timeo = option;  			break;  		case Opt_retrans: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0 || option == 0) +			if (nfs_get_option_ul(args, &option) || option == 0)  				goto out_invalid_value;  			mnt->retrans = option;  			break;  		case Opt_acregmin: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->acregmin = option;  			break;  		case Opt_acregmax: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->acregmax = option;  			break;  		case Opt_acdirmin: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->acdirmin = option;  			break;  		case Opt_acdirmax: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->acdirmax = option;  			break;  		case Opt_actimeo: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->acregmin = mnt->acregmax =  			mnt->acdirmin = mnt->acdirmax = option;  			break;  		case Opt_namelen: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			mnt->namlen = option;  			break;  		case Opt_mountport: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0 || option > USHRT_MAX) +			if (nfs_get_option_ul(args, &option) || +			    option > USHRT_MAX)  				goto out_invalid_value;  			mnt->mount_server.port = option;  			break;  		case Opt_mountvers: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0 || +			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_nfsvers: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) -				goto out_invalid_value; -			switch (option) { -			case NFS2_VERSION: -				mnt->flags &= ~NFS_MOUNT_VER3; -				mnt->version = 2; -				break; -			case NFS3_VERSION: -				mnt->flags |= NFS_MOUNT_VER3; -				mnt->version = 3; -				break; -#ifdef CONFIG_NFS_V4 -			case NFS4_VERSION: -				mnt->flags &= ~NFS_MOUNT_VER3; -				mnt->version = 4; -				break; -#endif -			default: -				goto out_invalid_value; -			} -			break;  		case Opt_minorversion: -			string = match_strdup(args); -			if (string == NULL) -				goto out_nomem; -			rc = strict_strtoul(string, 10, &option); -			kfree(string); -			if (rc != 0) +			if (nfs_get_option_ul(args, &option))  				goto out_invalid_value;  			if (option > NFS4_MAX_MINOR_VERSION)  				goto out_invalid_value; @@ -1307,6 +1411,15 @@ static int nfs_parse_mount_options(char *raw,  		/*  		 * 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) @@ -1333,21 +1446,18 @@ static int nfs_parse_mount_options(char *raw,  			case Opt_xprt_udp:  				mnt->flags &= ~NFS_MOUNT_TCP;  				mnt->nfs_server.protocol = XPRT_TRANSPORT_UDP; -				kfree(string);  				break;  			case Opt_xprt_tcp6:  				protofamily = AF_INET6;  			case Opt_xprt_tcp:  				mnt->flags |= NFS_MOUNT_TCP;  				mnt->nfs_server.protocol = XPRT_TRANSPORT_TCP; -				kfree(string);  				break;  			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); -				kfree(string);  				break;  			default:  				dfprintk(MOUNT, "NFS:   unrecognized " @@ -1355,6 +1465,7 @@ static int nfs_parse_mount_options(char *raw,  				kfree(string);  				return 0;  			} +			kfree(string);  			break;  		case Opt_mountproto:  			string = match_strdup(args); @@ -1388,7 +1499,7 @@ static int nfs_parse_mount_options(char *raw,  			if (string == NULL)  				goto out_nomem;  			mnt->nfs_server.addrlen = -				rpc_pton(string, strlen(string), +				rpc_pton(mnt->net, string, strlen(string),  					(struct sockaddr *)  					&mnt->nfs_server.address,  					sizeof(mnt->nfs_server.address)); @@ -1397,25 +1508,20 @@ static int nfs_parse_mount_options(char *raw,  				goto out_invalid_address;  			break;  		case Opt_clientaddr: -			string = match_strdup(args); -			if (string == NULL) +			if (nfs_get_option_str(args, &mnt->client_address))  				goto out_nomem; -			kfree(mnt->client_address); -			mnt->client_address = string;  			break;  		case Opt_mounthost: -			string = match_strdup(args); -			if (string == NULL) +			if (nfs_get_option_str(args, +					       &mnt->mount_server.hostname))  				goto out_nomem; -			kfree(mnt->mount_server.hostname); -			mnt->mount_server.hostname = string;  			break;  		case Opt_mountaddr:  			string = match_strdup(args);  			if (string == NULL)  				goto out_nomem;  			mnt->mount_server.addrlen = -				rpc_pton(string, strlen(string), +				rpc_pton(mnt->net, string, strlen(string),  					(struct sockaddr *)  					&mnt->mount_server.address,  					sizeof(mnt->mount_server.address)); @@ -1448,11 +1554,8 @@ static int nfs_parse_mount_options(char *raw,  			};  			break;  		case Opt_fscache_uniq: -			string = match_strdup(args); -			if (string == NULL) +			if (nfs_get_option_str(args, &mnt->fscache_uniq))  				goto out_nomem; -			kfree(mnt->fscache_uniq); -			mnt->fscache_uniq = string;  			mnt->options |= NFS_OPTION_FSCACHE;  			break;  		case Opt_local_lock: @@ -1507,9 +1610,16 @@ static int nfs_parse_mount_options(char *raw,  	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 -	 * familiies in the addr=/mountaddr= options. +	 * families in the addr=/mountaddr= options.  	 */  	if (protofamily != AF_UNSPEC &&  	    protofamily != mnt->nfs_server.address.ss_family) @@ -1540,6 +1650,14 @@ out_invalid_address:  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; @@ -1550,60 +1668,51 @@ out_security_failure:  }  /* - * Match the requested auth flavors with the list returned by - * the server.  Returns zero and sets the mount's authentication - * flavor on success; returns -EACCES if server does not support - * the requested flavor. + * 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.   */ -static int nfs_walk_authlist(struct nfs_parsed_mount_data *args, -			     struct nfs_mount_request *request) +static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args, +			rpc_authflavor_t *server_authlist, unsigned int count)  { -	unsigned int i, j, server_authlist_len = *(request->auth_flav_len); - -	/* -	 * Certain releases of Linux's mountd return an empty -	 * flavor list.  To prevent behavioral regression with -	 * these servers (ie. rejecting mounts that used to -	 * succeed), revert to pre-2.6.32 behavior (no checking) -	 * if the returned flavor list is empty. -	 */ -	if (server_authlist_len == 0) -		return 0; +	rpc_authflavor_t flavor = RPC_AUTH_MAXFLAVOR; +	unsigned int i;  	/* -	 * We avoid sophisticated negotiating here, as there are -	 * plenty of cases where we can get it wrong, providing -	 * either too little or too much security. +	 * If the sec= mount option is used, the specified flavor or AUTH_NULL +	 * must be in the list returned by the server.  	 * -	 * RFC 2623, section 2.7 suggests we SHOULD prefer the -	 * flavor listed first.  However, some servers list -	 * AUTH_NULL first.  Our caller plants AUTH_SYS, the -	 * preferred default, in args->auth_flavors[0] if user -	 * didn't specify sec= mount option. +	 * AUTH_NULL has a special meaning when it's in the server list - it +	 * means that the server will ignore the rpc creds, so any flavor +	 * can be used.  	 */ -	for (i = 0; i < args->auth_flavor_len; i++) -		for (j = 0; j < server_authlist_len; j++) -			if (args->auth_flavors[i] == request->auth_flavs[j]) { -				dfprintk(MOUNT, "NFS: using auth flavor %d\n", -					request->auth_flavs[j]); -				args->auth_flavors[0] = request->auth_flavs[j]; -				return 0; -			} +	for (i = 0; i < count; i++) { +		flavor = server_authlist[i]; + +		if (nfs_auth_info_match(&args->auth_info, flavor) || +		    flavor == RPC_AUTH_NULL) +			goto out; +	} -	dfprintk(MOUNT, "NFS: server does not support requested auth flavor\n"); -	nfs_umount(request); +	dfprintk(MOUNT, +		 "NFS: specified auth flavors not supported by server\n");  	return -EACCES; + +out: +	args->selected_flavor = flavor; +	dfprintk(MOUNT, "NFS: using auth flavor %u\n", args->selected_flavor); +	return 0;  }  /*   * Use the remote server's MOUNT service to request the NFS file handle   * corresponding to the provided path.   */ -static int nfs_try_mount(struct nfs_parsed_mount_data *args, -			 struct nfs_fh *root_fh) +static int nfs_request_mount(struct nfs_parsed_mount_data *args, +			     struct nfs_fh *root_fh, +			     rpc_authflavor_t *server_authlist, +			     unsigned int *server_authlist_len)  { -	rpc_authflavor_t server_authlist[NFS_MAX_SECFLAVORS]; -	unsigned int server_authlist_len = ARRAY_SIZE(server_authlist);  	struct nfs_mount_request request = {  		.sap		= (struct sockaddr *)  						&args->mount_server.address, @@ -1611,8 +1720,9 @@ static int nfs_try_mount(struct nfs_parsed_mount_data *args,  		.protocol	= args->mount_server.protocol,  		.fh		= root_fh,  		.noresvport	= args->flags & NFS_MOUNT_NORESVPORT, -		.auth_flav_len	= &server_authlist_len, +		.auth_flav_len	= server_authlist_len,  		.auth_flavs	= server_authlist, +		.net		= args->net,  	};  	int status; @@ -1654,107 +1764,153 @@ static int nfs_try_mount(struct nfs_parsed_mount_data *args,  		return status;  	} -	/* -	 * MNTv1 (NFSv2) does not support auth flavor negotiation. -	 */ -	if (args->mount_server.version != NFS_MNT3_VERSION) -		return 0; -	return nfs_walk_authlist(args, &request); +	return 0;  } -static int nfs_parse_simple_hostname(const char *dev_name, -				     char **hostname, size_t maxnamlen, -				     char **export_path, size_t maxpathlen) +static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_info, +					struct nfs_subversion *nfs_mod)  { -	size_t len; -	char *colon, *comma; - -	colon = strchr(dev_name, ':'); -	if (colon == NULL) -		goto out_bad_devname; - -	len = colon - dev_name; -	if (len > maxnamlen) -		goto out_hostname; +	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_parsed_mount_data *args = mount_info->parsed; +	rpc_authflavor_t authlist[NFS_MAX_SECFLAVORS]; +	unsigned int authlist_len = ARRAY_SIZE(authlist); + +	status = nfs_request_mount(args, mount_info->mntfh, authlist, +					&authlist_len); +	if (status) +		return ERR_PTR(status); -	/* N.B. caller will free nfs_server.hostname in all cases */ -	*hostname = kstrndup(dev_name, len, GFP_KERNEL); -	if (!*hostname) -		goto out_nomem; +	/* +	 * 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); +		dfprintk(MOUNT, "NFS: using auth flavor %u\n", +			 args->selected_flavor); +		if (status) +			return ERR_PTR(status); +		return nfs_mod->rpc_ops->create_server(mount_info, nfs_mod); +	} -	/* kill possible hostname list: not supported */ -	comma = strchr(*hostname, ','); -	if (comma != NULL) { -		if (comma == *hostname) -			goto out_bad_devname; -		*comma = '\0'; +	/* +	 * No sec= option was provided. RFC 2623, section 2.7 suggests we +	 * SHOULD prefer the flavor listed first. However, some servers list +	 * AUTH_NULL first. Avoid ever choosing AUTH_NULL. +	 */ +	for (i = 0; i < authlist_len; ++i) { +		rpc_authflavor_t flavor; +		struct rpcsec_gss_info info; + +		flavor = authlist[i]; +		switch (flavor) { +		case RPC_AUTH_UNIX: +			tried_auth_unix = true; +			break; +		case RPC_AUTH_NULL: +			auth_null_in_list = true; +			continue; +		default: +			if (rpcauth_get_gssinfo(flavor, &info) != 0) +				continue; +			/* Fallthrough */ +		} +		dfprintk(MOUNT, "NFS: attempting to use auth flavor %u\n", flavor); +		args->selected_flavor = flavor; +		server = nfs_mod->rpc_ops->create_server(mount_info, nfs_mod); +		if (!IS_ERR(server)) +			return server;  	} -	colon++; -	len = strlen(colon); -	if (len > maxpathlen) -		goto out_path; -	*export_path = kstrndup(colon, len, GFP_KERNEL); -	if (!*export_path) -		goto out_nomem; +	/* +	 * Nothing we tried so far worked. At this point, give up if we've +	 * already tried AUTH_UNIX or if the server's list doesn't contain +	 * AUTH_NULL +	 */ +	if (tried_auth_unix || !auth_null_in_list) +		return server; -	dfprintk(MOUNT, "NFS: MNTPATH: '%s'\n", *export_path); -	return 0; +	/* Last chance! Try AUTH_UNIX */ +	dfprintk(MOUNT, "NFS: attempting to use auth flavor %u\n", RPC_AUTH_UNIX); +	args->selected_flavor = RPC_AUTH_UNIX; +	return nfs_mod->rpc_ops->create_server(mount_info, nfs_mod); +} -out_bad_devname: -	dfprintk(MOUNT, "NFS: device name not in host:path format\n"); -	return -EINVAL; +struct dentry *nfs_try_mount(int flags, const char *dev_name, +			     struct nfs_mount_info *mount_info, +			     struct nfs_subversion *nfs_mod) +{ +	struct nfs_server *server; -out_nomem: -	dfprintk(MOUNT, "NFS: not enough memory to parse device name\n"); -	return -ENOMEM; +	if (mount_info->parsed->need_mount) +		server = nfs_try_mount_request(mount_info, nfs_mod); +	else +		server = nfs_mod->rpc_ops->create_server(mount_info, nfs_mod); -out_hostname: -	dfprintk(MOUNT, "NFS: server hostname too long\n"); -	return -ENAMETOOLONG; +	if (IS_ERR(server)) +		return ERR_CAST(server); -out_path: -	dfprintk(MOUNT, "NFS: export pathname too long\n"); -	return -ENAMETOOLONG; +	return nfs_fs_mount_common(server, flags, dev_name, mount_info, nfs_mod);  } +EXPORT_SYMBOL_GPL(nfs_try_mount);  /* - * Hostname has square brackets around it because it contains one or - * more colons.  We look for the first closing square bracket, and a - * colon must follow it. + * 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_protected_hostname(const char *dev_name, -					char **hostname, size_t maxnamlen, -					char **export_path, size_t maxpathlen) +static int nfs_parse_devname(const char *dev_name, +			     char **hostname, size_t maxnamlen, +			     char **export_path, size_t maxpathlen)  {  	size_t len; -	char *start, *end; +	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; -	start = (char *)(dev_name + 1); +		end = strchr(dev_name, ':'); +		if (end == NULL) +			goto out_bad_devname; +		len = end - dev_name; -	end = strchr(start, ']'); -	if (end == NULL) -		goto out_bad_devname; -	if (*(end + 1) != ':') -		goto out_bad_devname; +		/* kill possible hostname list: not supported */ +		comma = strchr(dev_name, ','); +		if (comma != NULL && comma < end) +			*comma = 0; +	} -	len = end - start;  	if (len > maxnamlen)  		goto out_hostname;  	/* N.B. caller will free nfs_server.hostname in all cases */ -	*hostname = kstrndup(start, len, GFP_KERNEL); +	*hostname = kstrndup(dev_name, len, GFP_KERNEL);  	if (*hostname == NULL)  		goto out_nomem; - -	end += 2; -	len = strlen(end); +	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: @@ -1775,29 +1931,6 @@ out_path:  }  /* - * 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) -{ -	if (*dev_name == '[') -		return nfs_parse_protected_hostname(dev_name, -						    hostname, maxnamlen, -						    export_path, maxpathlen); - -	return nfs_parse_simple_hostname(dev_name, -					 hostname, maxnamlen, -					 export_path, maxpathlen); -} - -/*   * Validate the NFS2/NFS3 mount data   * - fills in the mount root filehandle   * @@ -1813,17 +1946,19 @@ static int nfs_parse_devname(const char *dev_name,   * + breaking back: trying proto=udp after proto=tcp, v2 after v3,   *   mountproto=tcp after mountproto=udp, and so on   */ -static int nfs_validate_mount_data(void *options, -				   struct nfs_parsed_mount_data *args, -				   struct nfs_fh *mntfh, -				   const char *dev_name) +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; @@ -1834,6 +1969,8 @@ static int nfs_validate_mount_data(void *options,  			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; @@ -1861,7 +1998,7 @@ static int nfs_validate_mount_data(void *options,  		 * can deal with.  		 */  		args->flags		= data->flags & NFS_MOUNT_FLAGMASK; -		args->flags		|= NFS_MOUNT_LEGACY_INTERFACE; +		args->flags		|= extra_flags;  		args->rsize		= data->rsize;  		args->wsize		= data->wsize;  		args->timeo		= data->timeo; @@ -1870,9 +2007,11 @@ static int nfs_validate_mount_data(void *options,  		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; @@ -1884,7 +2023,9 @@ static int nfs_validate_mount_data(void *options,  		args->bsize		= data->bsize;  		if (data->flags & NFS_MOUNT_SECFLAVOUR) -			args->auth_flavors[0] = data->pseudoflavor; +			args->selected_flavor = data->pseudoflavor; +		else +			args->selected_flavor = RPC_AUTH_UNIX;  		if (!args->nfs_server.hostname)  			goto out_nomem; @@ -1921,46 +2062,11 @@ static int nfs_validate_mount_data(void *options,  		}  		break; -	default: { -		int status; - -		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) -#ifdef CONFIG_NFS_V4 -			return nfs4_validate_text_mount_data(options, -							     args, dev_name); -#else -			goto out_v4_not_compiled; -#endif - -		nfs_set_port(sap, &args->nfs_server.port, 0); - -		nfs_set_mount_transport_protocol(args); - -		status = nfs_parse_devname(dev_name, -					   &args->nfs_server.hostname, -					   PAGE_SIZE, -					   &args->nfs_server.export_path, -					   NFS_MAXPATHLEN); -		if (!status) -			status = nfs_try_mount(args, mntfh); - -		kfree(args->nfs_server.export_path); -		args->nfs_server.export_path = NULL; - -		if (status) -			return status; - -		break; -		} +	default: +		return NFS_TEXT_DATA;  	} -#ifndef CONFIG_NFS_V3 +#if !IS_ENABLED(CONFIG_NFS_V3)  	if (args->version == 3)  		goto out_v3_not_compiled;  #endif /* !CONFIG_NFS_V3 */ @@ -1980,18 +2086,12 @@ out_no_sec:  	dfprintk(MOUNT, "NFS: nfs_mount_data version supports only AUTH_SYS\n");  	return -EINVAL; -#ifndef CONFIG_NFS_V3 +#if !IS_ENABLED(CONFIG_NFS_V3)  out_v3_not_compiled:  	dfprintk(MOUNT, "NFS: NFSv3 is not compiled into kernel\n");  	return -EPROTONOSUPPORT;  #endif /* !CONFIG_NFS_V3 */ -#ifndef CONFIG_NFS_V4 -out_v4_not_compiled: -	dfprintk(MOUNT, "NFS: NFSv4 is not compiled into kernel\n"); -	return -EPROTONOSUPPORT; -#endif /* !CONFIG_NFS_V4 */ -  out_nomem:  	dfprintk(MOUNT, "NFS: not enough memory to handle mount options\n");  	return -ENOMEM; @@ -2005,15 +2105,104 @@ out_invalid_fh:  	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_MOUNT_CMP_FLAGMASK ~(NFS_MOUNT_INTR \ +		| NFS_MOUNT_SECURE \ +		| NFS_MOUNT_TCP \ +		| NFS_MOUNT_VER3 \ +		| NFS_MOUNT_KERBEROS \ +		| NFS_MOUNT_NONLM \ +		| NFS_MOUNT_BROKEN_SUID \ +		| NFS_MOUNT_STRICTLOCK \ +		| NFS_MOUNT_UNSHARED \ +		| NFS_MOUNT_NORESVPORT \ +		| NFS_MOUNT_LEGACY_INTERFACE) +  static int  nfs_compare_remount_data(struct nfs_server *nfss,  			 struct nfs_parsed_mount_data *data)  { -	if (data->flags != nfss->flags || +	if ((data->flags ^ nfss->flags) & NFS_MOUNT_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 || -	    data->auth_flavors[0] != nfss->client->cl_auth->au_flavor || +	    data->selected_flavor != nfss->client->cl_auth->au_flavor ||  	    data->acregmin != nfss->acregmin / HZ ||  	    data->acregmax != nfss->acregmax / HZ ||  	    data->acdirmin != nfss->acdirmin / HZ || @@ -2028,7 +2217,7 @@ nfs_compare_remount_data(struct nfs_server *nfss,  	return 0;  } -static int +int  nfs_remount(struct super_block *sb, int *flags, char *raw_data)  {  	int error; @@ -2038,6 +2227,8 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)  	struct nfs4_mount_data *options4 = (struct nfs4_mount_data *)raw_data;  	u32 nfsvers = nfss->nfs_client->rpc_ops->version; +	sync_filesystem(sb); +  	/*  	 * Userspace mount programs that send binary options generally send  	 * them populated with default values. We have no way to know which @@ -2058,7 +2249,8 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)  	data->rsize = nfss->rsize;  	data->wsize = nfss->wsize;  	data->retrans = nfss->client->cl_timeout->to_retries; -	data->auth_flavors[0] = nfss->client->cl_auth->au_flavor; +	data->selected_flavor = nfss->client->cl_auth->au_flavor; +	data->auth_info = nfss->auth_info;  	data->acregmin = nfss->acregmin / HZ;  	data->acregmax = nfss->acregmax / HZ;  	data->acdirmin = nfss->acdirmin / HZ; @@ -2066,25 +2258,38 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data)  	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);  	/* overwrite those values with any that were specified */ -	error = nfs_parse_mount_options((char *)options, data); -	if (error < 0) +	error = -EINVAL; +	if (!nfs_parse_mount_options((char *)options, data))  		goto out; +	/* +	 * noac is a special case. It implies -o sync, but that's not +	 * necessarily reflected in the mtab options. do_remount_sb +	 * 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) +		*flags |= MS_SYNCHRONOUS; +  	/* compare new mount options with old ones */  	error = nfs_compare_remount_data(nfss, data);  out:  	kfree(data);  	return error;  } +EXPORT_SYMBOL_GPL(nfs_remount);  /*   * Initialise the common bits of the superblock   */ -static inline void nfs_initialise_sb(struct super_block *sb) +inline void nfs_initialise_sb(struct super_block *sb)  {  	struct nfs_server *server = NFS_SB(sb); @@ -2092,15 +2297,12 @@ static inline void nfs_initialise_sb(struct super_block *sb)  	/* We probably want something more informative here */  	snprintf(sb->s_id, sizeof(sb->s_id), -		 "%x:%x", MAJOR(sb->s_dev), MINOR(sb->s_dev)); +		 "%u:%u", MAJOR(sb->s_dev), MINOR(sb->s_dev));  	if (sb->s_blocksize == 0)  		sb->s_blocksize = nfs_block_bits(server->wsize,  						 &sb->s_blocksize_bits); -	if (server->flags & NFS_MOUNT_NOAC) -		sb->s_flags |= MS_SYNCHRONOUS; -  	sb->s_bdi = &server->backing_dev_info;  	nfs_super_set_maxbytes(sb, server->maxfilesize); @@ -2109,17 +2311,19 @@ static inline void nfs_initialise_sb(struct super_block *sb)  /*   * Finish setting up an NFS2/3 superblock   */ -static void nfs_fill_super(struct super_block *sb, -			   struct nfs_parsed_mount_data *data) +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_server *server = NFS_SB(sb);  	sb->s_blocksize_bits = 0;  	sb->s_blocksize = 0; -	if (data->bsize) +	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 (server->nfs_client->rpc_ops->version == 3) { +	if (server->nfs_client->rpc_ops->version != 2) {  		/* The VFS shouldn't apply the umask to mode bits. We will do  		 * so ourselves when necessary.  		 */ @@ -2127,31 +2331,32 @@ static void nfs_fill_super(struct super_block *sb,  		sb->s_time_gran = 1;  	} -	sb->s_op = &nfs_sops;   	nfs_initialise_sb(sb);  } +EXPORT_SYMBOL_GPL(nfs_fill_super);  /* - * Finish setting up a cloned NFS2/3 superblock + * Finish setting up a cloned NFS2/3/4 superblock   */ -static void nfs_clone_super(struct super_block *sb, -			    const struct super_block *old_sb) +void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)  { +	const struct super_block *old_sb = mount_info->cloned->sb;  	struct nfs_server *server = NFS_SB(sb);  	sb->s_blocksize_bits = old_sb->s_blocksize_bits;  	sb->s_blocksize = old_sb->s_blocksize;  	sb->s_maxbytes = old_sb->s_maxbytes; +	sb->s_xattr = old_sb->s_xattr; +	sb->s_op = old_sb->s_op; +	sb->s_time_gran = 1; -	if (server->nfs_client->rpc_ops->version == 3) { +	if (server->nfs_client->rpc_ops->version != 2) {  		/* The VFS shouldn't apply the umask to mode bits. We will do  		 * so ourselves when necessary.  		 */  		sb->s_flags |= MS_POSIXACL; -		sb->s_time_gran = 1;  	} -	sb->s_op = old_sb->s_op;   	nfs_initialise_sb(sb);  } @@ -2165,7 +2370,7 @@ static int nfs_compare_mount_options(const struct super_block *s, const struct n  		goto Ebusy;  	if (a->nfs_client != b->nfs_client)  		goto Ebusy; -	if (a->flags != b->flags) +	if ((a->flags ^ b->flags) & NFS_MOUNT_CMP_FLAGMASK)  		goto Ebusy;  	if (a->wsize != b->wsize)  		goto Ebusy; @@ -2179,7 +2384,8 @@ static int nfs_compare_mount_options(const struct super_block *s, const struct n  		goto Ebusy;  	if (a->acdirmax != b->acdirmax)  		goto Ebusy; -	if (clnt_a->cl_auth->au_flavor != clnt_b->cl_auth->au_flavor) +	if (b->auth_info.flavor_len > 0 && +	   clnt_a->cl_auth->au_flavor != clnt_b->cl_auth->au_flavor)  		goto Ebusy;  	return 1;  Ebusy: @@ -2199,6 +2405,7 @@ static int nfs_set_super(struct super_block *s, void *data)  	s->s_flags = sb_mntdata->mntflags;  	s->s_fs_info = server; +	s->s_d_op = server->nfs_client->rpc_ops->dentry_ops;  	ret = set_anon_super(s, server);  	if (ret == 0)  		server->s_dev = s->s_dev; @@ -2258,61 +2465,111 @@ static int nfs_compare_super(struct super_block *sb, void *data)  	return nfs_compare_mount_options(sb, server, mntflags);  } +#ifdef CONFIG_NFS_FSCACHE +static void nfs_get_cache_cookie(struct super_block *sb, +				 struct nfs_parsed_mount_data *parsed, +				 struct nfs_clone_mount *cloned) +{ +	struct nfs_server *nfss = NFS_SB(sb); +	char *uniq = NULL; +	int ulen = 0; + +	nfss->fscache_key = NULL; +	nfss->fscache = NULL; + +	if (parsed) { +		if (!(parsed->options & NFS_OPTION_FSCACHE)) +			return; +		if (parsed->fscache_uniq) { +			uniq = parsed->fscache_uniq; +			ulen = strlen(parsed->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_parsed_mount_data *parsed, +				 struct nfs_clone_mount *cloned) +{ +} +#endif +  static int nfs_bdi_register(struct nfs_server *server)  {  	return bdi_register_dev(&server->backing_dev_info, server->s_dev);  } -static int nfs_get_sb(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt) +int nfs_set_sb_security(struct super_block *s, struct dentry *mntroot, +			struct nfs_mount_info *mount_info) +{ +	int error; +	unsigned long kflags = 0, kflags_out = 0; +	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, +						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; +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) +{ +	/* clone any lsm security options from the parent to the new sb */ +	if (mntroot->d_inode->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) +		return -ESTALE; +	return security_sb_clone_mnt_opts(mount_info->cloned->sb, s); +} +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)  { -	struct nfs_server *server = NULL;  	struct super_block *s; -	struct nfs_parsed_mount_data *data; -	struct nfs_fh *mntfh; -	struct dentry *mntroot; +	struct dentry *mntroot = ERR_PTR(-ENOMEM);  	int (*compare_super)(struct super_block *, void *) = nfs_compare_super;  	struct nfs_sb_mountdata sb_mntdata = {  		.mntflags = flags, +		.server = server,  	}; -	int error = -ENOMEM; - -	data = nfs_alloc_parsed_mount_data(3); -	mntfh = nfs_alloc_fhandle(); -	if (data == NULL || mntfh == NULL) -		goto out_free_fh; - -	security_init_mnt_opts(&data->lsm_opts); - -	/* Validate the mount data */ -	error = nfs_validate_mount_data(raw_data, data, mntfh, dev_name); -	if (error < 0) -		goto out; - -#ifdef CONFIG_NFS_V4 -	if (data->version == 4) { -		error = nfs4_try_mount(flags, dev_name, data, mnt); -		kfree(data->client_address); -		kfree(data->nfs_server.export_path); -		goto out; -	} -#endif	/* CONFIG_NFS_V4 */ - -	/* Get a volume representation */ -	server = nfs_create_server(data, mntfh); -	if (IS_ERR(server)) { -		error = PTR_ERR(server); -		goto out; -	} -	sb_mntdata.server = server; +	int error;  	if (server->flags & NFS_MOUNT_UNSHARED)  		compare_super = NULL; +	/* -o noac implies -o sync */ +	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) +			sb_mntdata.mntflags |= MS_SYNCHRONOUS; +  	/* Get a superblock - note that we may end up sharing one that already exists */ -	s = sget(fs_type, compare_super, nfs_set_super, &sb_mntdata); +	s = sget(nfs_mod->nfs_fs, compare_super, nfs_set_super, flags, &sb_mntdata);  	if (IS_ERR(s)) { -		error = PTR_ERR(s); +		mntroot = ERR_CAST(s);  		goto out_err_nosb;  	} @@ -2321,41 +2578,31 @@ static int nfs_get_sb(struct file_system_type *fs_type,  		server = NULL;  	} else {  		error = nfs_bdi_register(server); -		if (error) +		if (error) { +			mntroot = ERR_PTR(error);  			goto error_splat_bdi; +		} +		server->super = s;  	}  	if (!s->s_root) {  		/* initial superblock/root creation */ -		nfs_fill_super(s, data); -		nfs_fscache_get_super_cookie( -			s, data ? data->fscache_uniq : NULL, NULL); +		mount_info->fill_super(s, mount_info); +		nfs_get_cache_cookie(s, mount_info->parsed, mount_info->cloned);  	} -	mntroot = nfs_get_root(s, mntfh); -	if (IS_ERR(mntroot)) { -		error = PTR_ERR(mntroot); +	mntroot = nfs_get_root(s, mount_info->mntfh, dev_name); +	if (IS_ERR(mntroot))  		goto error_splat_super; -	} -	error = security_sb_set_mnt_opts(s, &data->lsm_opts); +	error = mount_info->set_security(s, mntroot, mount_info);  	if (error)  		goto error_splat_root;  	s->s_flags |= MS_ACTIVE; -	mnt->mnt_sb = s; -	mnt->mnt_root = mntroot; -	error = 0;  out: -	kfree(data->nfs_server.hostname); -	kfree(data->mount_server.hostname); -	kfree(data->fscache_uniq); -	security_free_mnt_opts(&data->lsm_opts); -out_free_fh: -	nfs_free_fhandle(mntfh); -	kfree(data); -	return error; +	return mntroot;  out_err_nosb:  	nfs_free_server(server); @@ -2363,6 +2610,7 @@ out_err_nosb:  error_splat_root:  	dput(mntroot); +	mntroot = ERR_PTR(error);  error_splat_super:  	if (server && !s->s_root)  		bdi_unregister(&server->backing_dev_info); @@ -2370,22 +2618,65 @@ error_splat_bdi:  	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.parsed = nfs_alloc_parsed_mount_data(); +	mount_info.mntfh = nfs_alloc_fhandle(); +	if (mount_info.parsed == 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); +	if (error == NFS_TEXT_DATA) +		error = nfs_validate_text_mount_data(raw_data, mount_info.parsed, dev_name); +	if (error < 0) { +		mntroot = ERR_PTR(error); +		goto out; +	} + +	nfs_mod = get_nfs_version(mount_info.parsed->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.parsed); +	nfs_free_fhandle(mount_info.mntfh); +	return mntroot; +} +EXPORT_SYMBOL_GPL(nfs_fs_mount);  /*   * Ensure that we unregister the bdi before kill_anon_super   * releases the device name   */ -static void nfs_put_super(struct super_block *s) +void nfs_put_super(struct super_block *s)  {  	struct nfs_server *server = NFS_SB(s);  	bdi_unregister(&server->backing_dev_info);  } +EXPORT_SYMBOL_GPL(nfs_put_super);  /*   * Destroy an NFS2/3 superblock   */ -static void nfs_kill_super(struct super_block *s) +void nfs_kill_super(struct super_block *s)  {  	struct nfs_server *server = NFS_SB(s); @@ -2393,118 +2684,44 @@ static void nfs_kill_super(struct super_block *s)  	nfs_fscache_release_super_cookie(s);  	nfs_free_server(server);  } +EXPORT_SYMBOL_GPL(nfs_kill_super);  /* - * Clone an NFS2/3 server record on xdev traversal (FSID-change) + * 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 super_block *s; -	struct nfs_server *server; -	struct dentry *mntroot; -	int (*compare_super)(struct super_block *, void *) = nfs_compare_super; -	struct nfs_sb_mountdata sb_mntdata = { -		.mntflags = flags, +	struct nfs_mount_info mount_info = { +		.fill_super = nfs_clone_super, +		.set_security = nfs_clone_sb_security, +		.cloned = data,  	}; -	int error; +	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"); -	/* create a new volume representation */ -	server = nfs_clone_server(NFS_SB(data->sb), data->fh, data->fattr); -	if (IS_ERR(server)) { -		error = PTR_ERR(server); -		goto out_err_noserver; -	} -	sb_mntdata.server = server; +	mount_info.mntfh = mount_info.cloned->fh; -	if (server->flags & NFS_MOUNT_UNSHARED) -		compare_super = NULL; - -	/* Get a superblock - note that we may end up sharing one that already exists */ -	s = sget(&nfs_fs_type, compare_super, nfs_set_super, &sb_mntdata); -	if (IS_ERR(s)) { -		error = PTR_ERR(s); -		goto out_err_nosb; -	} - -	if (s->s_fs_info != server) { -		nfs_free_server(server); -		server = NULL; -	} else { -		error = nfs_bdi_register(server); -		if (error) -			goto error_splat_bdi; -	} - -	if (!s->s_root) { -		/* initial superblock/root creation */ -		nfs_clone_super(s, data->sb); -		nfs_fscache_get_super_cookie(s, NULL, data); -	} - -	mntroot = nfs_get_root(s, data->fh); -	if (IS_ERR(mntroot)) { -		error = PTR_ERR(mntroot); -		goto error_splat_super; -	} -	if (mntroot->d_inode->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) { -		dput(mntroot); -		error = -ESTALE; -		goto error_splat_super; -	} - -	s->s_flags |= MS_ACTIVE; +	/* create a new volume representation */ +	server = nfs_mod->rpc_ops->clone_server(NFS_SB(data->sb), data->fh, data->fattr, data->authflavor); -	/* clone any lsm security options from the parent to the new sb */ -	security_sb_clone_mnt_opts(data->sb, s); +	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() = 0\n"); +	dprintk("<-- nfs_xdev_mount() = %ld\n", +			IS_ERR(mntroot) ? PTR_ERR(mntroot) : 0L);  	return mntroot; - -out_err_nosb: -	nfs_free_server(server); -out_err_noserver: -	dprintk("<-- nfs_xdev_mount() = %d [error]\n", error); -	return ERR_PTR(error); - -error_splat_super: -	if (server && !s->s_root) -		bdi_unregister(&server->backing_dev_info); -error_splat_bdi: -	deactivate_locked_super(s); -	dprintk("<-- nfs_xdev_mount() = %d [splat]\n", error); -	return ERR_PTR(error); -} - -#ifdef CONFIG_NFS_V4 - -/* - * Finish setting up a cloned NFS4 superblock - */ -static void nfs4_clone_super(struct super_block *sb, -			    const struct super_block *old_sb) -{ -	sb->s_blocksize_bits = old_sb->s_blocksize_bits; -	sb->s_blocksize = old_sb->s_blocksize; -	sb->s_maxbytes = old_sb->s_maxbytes; -	sb->s_time_gran = 1; -	sb->s_op = old_sb->s_op; - 	nfs_initialise_sb(sb);  } -/* - * Set up an NFS4 superblock - */ -static void nfs4_fill_super(struct super_block *sb) -{ -	sb->s_time_gran = 1; -	sb->s_op = &nfs4_sops; -	nfs_initialise_sb(sb); -} +#if IS_ENABLED(CONFIG_NFS_V4)  static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)  { @@ -2512,43 +2729,6 @@ static void nfs4_validate_mount_flags(struct nfs_parsed_mount_data *args)  			 NFS_MOUNT_LOCAL_FLOCK|NFS_MOUNT_LOCAL_FCNTL);  } -static int nfs4_validate_text_mount_data(void *options, -					 struct nfs_parsed_mount_data *args, -					 const char *dev_name) -{ -	struct sockaddr *sap = (struct sockaddr *)&args->nfs_server.address; - -	nfs_set_port(sap, &args->nfs_server.port, NFS_PORT); - -	nfs_validate_transport_protocol(args); - -	nfs4_validate_mount_flags(args); - -	if (args->version != 4) { -		dfprintk(MOUNT, -			 "NFS4: Illegal mount version\n"); -		return -EINVAL; -	} - -	if (args->auth_flavor_len > 1) { -		dfprintk(MOUNT, -			 "NFS4: Too many RPC auth flavours specified\n"); -		return -EINVAL; -	} - -	if (args->client_address == NULL) { -		dfprintk(MOUNT, -			 "NFS4: mount program didn't pass callback address\n"); -		return -EINVAL; -	} - -	return nfs_parse_devname(dev_name, -				   &args->nfs_server.hostname, -				   NFS4_MAXNAMLEN, -				   &args->nfs_server.export_path, -				   NFS4_MAXPATHLEN); -} -  /*   * Validate NFSv4 mount options   */ @@ -2563,6 +2743,8 @@ static int nfs4_validate_mount_data(void *options,  	if (data == NULL)  		goto out_no_data; +	args->version = 4; +  	switch (data->version) {  	case 1:  		if (data->host_addrlen > sizeof(args->nfs_server.address)) @@ -2574,15 +2756,19 @@ static int nfs4_validate_mount_data(void *options,  			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(&args->auth_flavors[0], +			if (copy_from_user(&pseudoflavor,  					   data->auth_flavours, -					   sizeof(args->auth_flavors[0]))) +					   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)) @@ -2616,16 +2802,12 @@ static int nfs4_validate_mount_data(void *options,  		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: -		if (nfs_parse_mount_options((char *)options, args) == 0) -			return -EINVAL; - -		if (!nfs_verify_server_address(sap)) -			return -EINVAL; - -		return nfs4_validate_text_mount_data(options, args, dev_name); +		return NFS_TEXT_DATA;  	}  	return 0; @@ -2642,535 +2824,76 @@ out_inval_auth:  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;  }  /* - * Get the superblock for the NFS4 root partition + * NFS v4 module parameters need to stay in the + * NFS client for backwards compatibility   */ -static struct dentry * -nfs4_remote_mount(struct file_system_type *fs_type, int flags, -		  const char *dev_name, void *raw_data) -{ -	struct nfs_parsed_mount_data *data = raw_data; -	struct super_block *s; -	struct nfs_server *server; -	struct nfs_fh *mntfh; -	struct dentry *mntroot; -	int (*compare_super)(struct super_block *, void *) = nfs_compare_super; -	struct nfs_sb_mountdata sb_mntdata = { -		.mntflags = flags, -	}; -	int error = -ENOMEM; - -	mntfh = nfs_alloc_fhandle(); -	if (data == NULL || mntfh == NULL) -		goto out_free_fh; - -	security_init_mnt_opts(&data->lsm_opts); - -	/* Get a volume representation */ -	server = nfs4_create_server(data, mntfh); -	if (IS_ERR(server)) { -		error = PTR_ERR(server); -		goto out; -	} -	sb_mntdata.server = server; - -	if (server->flags & NFS4_MOUNT_UNSHARED) -		compare_super = NULL; - -	/* Get a superblock - note that we may end up sharing one that already exists */ -	s = sget(&nfs4_fs_type, compare_super, nfs_set_super, &sb_mntdata); -	if (IS_ERR(s)) { -		error = PTR_ERR(s); -		goto out_free; -	} - -	if (s->s_fs_info != server) { -		nfs_free_server(server); -		server = NULL; -	} else { -		error = nfs_bdi_register(server); -		if (error) -			goto error_splat_bdi; -	} - -	if (!s->s_root) { -		/* initial superblock/root creation */ -		nfs4_fill_super(s); -		nfs_fscache_get_super_cookie( -			s, data ? data->fscache_uniq : NULL, NULL); -	} - -	mntroot = nfs4_get_root(s, mntfh); -	if (IS_ERR(mntroot)) { -		error = PTR_ERR(mntroot); -		goto error_splat_super; -	} - -	error = security_sb_set_mnt_opts(s, &data->lsm_opts); -	if (error) -		goto error_splat_root; - -	s->s_flags |= MS_ACTIVE; - -	security_free_mnt_opts(&data->lsm_opts); -	nfs_free_fhandle(mntfh); -	return mntroot; - -out: -	security_free_mnt_opts(&data->lsm_opts); -out_free_fh: -	nfs_free_fhandle(mntfh); -	return ERR_PTR(error); - -out_free: -	nfs_free_server(server); -	goto out; - -error_splat_root: -	dput(mntroot); -error_splat_super: -	if (server && !s->s_root) -		bdi_unregister(&server->backing_dev_info); -error_splat_bdi: -	deactivate_locked_super(s); -	goto out; -} - -static struct vfsmount *nfs_do_root_mount(struct file_system_type *fs_type, -		int flags, void *data, const char *hostname) -{ -	struct vfsmount *root_mnt; -	char *root_devname; -	size_t len; - -	len = strlen(hostname) + 3; -	root_devname = kmalloc(len, GFP_KERNEL); -	if (root_devname == NULL) -		return ERR_PTR(-ENOMEM); -	snprintf(root_devname, len, "%s:/", hostname); -	root_mnt = vfs_kern_mount(fs_type, flags, root_devname, data); -	kfree(root_devname); -	return root_mnt; -} - -static void nfs_fix_devname(const struct path *path, struct vfsmount *mnt) -{ -	char *page = (char *) __get_free_page(GFP_KERNEL); -	char *devname, *tmp; - -	if (page == NULL) -		return; -	devname = nfs_path(path->mnt->mnt_devname, -			path->mnt->mnt_root, path->dentry, -			page, PAGE_SIZE); -	if (IS_ERR(devname)) -		goto out_freepage; -	tmp = kstrdup(devname, GFP_KERNEL); -	if (tmp == NULL) -		goto out_freepage; -	kfree(mnt->mnt_devname); -	mnt->mnt_devname = tmp; -out_freepage: -	free_page((unsigned long)page); -} - -struct nfs_referral_count { -	struct list_head list; -	const struct task_struct *task; -	unsigned int referral_count; -}; - -static LIST_HEAD(nfs_referral_count_list); -static DEFINE_SPINLOCK(nfs_referral_count_list_lock); - -static struct nfs_referral_count *nfs_find_referral_count(void) -{ -	struct nfs_referral_count *p; - -	list_for_each_entry(p, &nfs_referral_count_list, list) { -		if (p->task == current) -			return p; -	} -	return NULL; -} - -#define NFS_MAX_NESTED_REFERRALS 2 - -static int nfs_referral_loop_protect(void) +unsigned int nfs_callback_set_tcpport; +unsigned short nfs_callback_tcpport; +/* Default cache timeout is 10 minutes */ +unsigned int nfs_idmap_cache_timeout = 600; +/* Turn off NFSv4 uid/gid mapping when using AUTH_SYS */ +bool nfs4_disable_idmapping = true; +unsigned short max_session_slots = NFS4_DEF_SLOT_TABLE_SIZE; +unsigned short send_implementation_id = 1; +char nfs4_client_id_uniquifier[NFS4_CLIENT_ID_UNIQ_LEN] = ""; +bool recover_lost_locks = false; + +EXPORT_SYMBOL_GPL(nfs_callback_set_tcpport); +EXPORT_SYMBOL_GPL(nfs_callback_tcpport); +EXPORT_SYMBOL_GPL(nfs_idmap_cache_timeout); +EXPORT_SYMBOL_GPL(nfs4_disable_idmapping); +EXPORT_SYMBOL_GPL(max_session_slots); +EXPORT_SYMBOL_GPL(send_implementation_id); +EXPORT_SYMBOL_GPL(nfs4_client_id_uniquifier); +EXPORT_SYMBOL_GPL(recover_lost_locks); + +#define NFS_CALLBACK_MAXPORTNR (65535U) + +static int param_set_portnr(const char *val, const struct kernel_param *kp)  { -	struct nfs_referral_count *p, *new; -	int ret = -ENOMEM; - -	new = kmalloc(sizeof(*new), GFP_KERNEL); -	if (!new) -		goto out; -	new->task = current; -	new->referral_count = 1; - -	ret = 0; -	spin_lock(&nfs_referral_count_list_lock); -	p = nfs_find_referral_count(); -	if (p != NULL) { -		if (p->referral_count >= NFS_MAX_NESTED_REFERRALS) -			ret = -ELOOP; -		else -			p->referral_count++; -	} else { -		list_add(&new->list, &nfs_referral_count_list); -		new = NULL; -	} -	spin_unlock(&nfs_referral_count_list_lock); -	kfree(new); -out: -	return ret; -} - -static void nfs_referral_loop_unprotect(void) -{ -	struct nfs_referral_count *p; - -	spin_lock(&nfs_referral_count_list_lock); -	p = nfs_find_referral_count(); -	p->referral_count--; -	if (p->referral_count == 0) -		list_del(&p->list); -	else -		p = NULL; -	spin_unlock(&nfs_referral_count_list_lock); -	kfree(p); -} - -static int nfs_follow_remote_path(struct vfsmount *root_mnt, -		const char *export_path, struct vfsmount *mnt_target) -{ -	struct nameidata *nd = NULL; -	struct mnt_namespace *ns_private; -	struct super_block *s; +	unsigned long num;  	int ret; -	nd = kmalloc(sizeof(*nd), GFP_KERNEL); -	if (nd == NULL) -		return -ENOMEM; - -	ns_private = create_mnt_ns(root_mnt); -	ret = PTR_ERR(ns_private); -	if (IS_ERR(ns_private)) -		goto out_mntput; - -	ret = nfs_referral_loop_protect(); -	if (ret != 0) -		goto out_put_mnt_ns; - -	ret = vfs_path_lookup(root_mnt->mnt_root, root_mnt, -			export_path, LOOKUP_FOLLOW, nd); - -	nfs_referral_loop_unprotect(); -	put_mnt_ns(ns_private); - -	if (ret != 0) -		goto out_err; - -	s = nd->path.mnt->mnt_sb; -	atomic_inc(&s->s_active); -	mnt_target->mnt_sb = s; -	mnt_target->mnt_root = dget(nd->path.dentry); - -	/* Correct the device pathname */ -	nfs_fix_devname(&nd->path, mnt_target); - -	path_put(&nd->path); -	kfree(nd); -	down_write(&s->s_umount); +	if (!val) +		return -EINVAL; +	ret = kstrtoul(val, 0, &num); +	if (ret == -EINVAL || num > NFS_CALLBACK_MAXPORTNR) +		return -EINVAL; +	*((unsigned int *)kp->arg) = num;  	return 0; -out_put_mnt_ns: -	put_mnt_ns(ns_private); -out_mntput: -	mntput(root_mnt); -out_err: -	kfree(nd); -	return ret; -} - -static int nfs4_try_mount(int flags, const char *dev_name, -			 struct nfs_parsed_mount_data *data, -			 struct vfsmount *mnt) -{ -	char *export_path; -	struct vfsmount *root_mnt; -	int error; - -	dfprintk(MOUNT, "--> nfs4_try_mount()\n"); - -	export_path = data->nfs_server.export_path; -	data->nfs_server.export_path = "/"; -	root_mnt = nfs_do_root_mount(&nfs4_remote_fs_type, flags, data, -			data->nfs_server.hostname); -	data->nfs_server.export_path = export_path; - -	error = PTR_ERR(root_mnt); -	if (IS_ERR(root_mnt)) -		goto out; - -	error = nfs_follow_remote_path(root_mnt, export_path, mnt); - -out: -	dfprintk(MOUNT, "<-- nfs4_try_mount() = %d%s\n", error, -			error != 0 ? " [error]" : ""); -	return error;  } +static struct kernel_param_ops param_ops_portnr = { +	.set = param_set_portnr, +	.get = param_get_uint, +}; +#define param_check_portnr(name, p) __param_check(name, p, unsigned int); + +module_param_named(callback_tcpport, nfs_callback_set_tcpport, portnr, 0644); +module_param(nfs_idmap_cache_timeout, int, 0644); +module_param(nfs4_disable_idmapping, bool, 0644); +module_param_string(nfs4_unique_id, nfs4_client_id_uniquifier, +			NFS4_CLIENT_ID_UNIQ_LEN, 0600); +MODULE_PARM_DESC(nfs4_disable_idmapping, +		"Turn off NFSv4 idmapping when using 'sec=sys'"); +module_param(max_session_slots, ushort, 0644); +MODULE_PARM_DESC(max_session_slots, "Maximum number of outstanding NFSv4.1 " +		"requests the client will negotiate"); +module_param(send_implementation_id, ushort, 0644); +MODULE_PARM_DESC(send_implementation_id, +		"Send implementation ID with NFSv4.1 exchange_id"); +MODULE_PARM_DESC(nfs4_unique_id, "nfs_client_id4 uniquifier string"); + +module_param(recover_lost_locks, bool, 0644); +MODULE_PARM_DESC(recover_lost_locks, +		 "If the server reports that a lock might be lost, " +		 "try to recover it risking data corruption."); -/* - * Get the superblock for an NFS4 mountpoint - */ -static int nfs4_get_sb(struct file_system_type *fs_type, -	int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt) -{ -	struct nfs_parsed_mount_data *data; -	int error = -ENOMEM; - -	data = nfs_alloc_parsed_mount_data(4); -	if (data == NULL) -		goto out_free_data; - -	/* Validate the mount data */ -	error = nfs4_validate_mount_data(raw_data, data, dev_name); -	if (error < 0) -		goto out; - -	error = nfs4_try_mount(flags, dev_name, data, mnt); - -out: -	kfree(data->client_address); -	kfree(data->nfs_server.export_path); -	kfree(data->nfs_server.hostname); -	kfree(data->fscache_uniq); -out_free_data: -	kfree(data); -	dprintk("<-- nfs4_get_sb() = %d%s\n", error, -			error != 0 ? " [error]" : ""); -	return error; -} - -static void nfs4_kill_super(struct super_block *sb) -{ -	struct nfs_server *server = NFS_SB(sb); - -	dprintk("--> %s\n", __func__); -	nfs_super_return_all_delegations(sb); -	kill_anon_super(sb); -	nfs_fscache_release_super_cookie(sb); -	nfs_free_server(server); -	dprintk("<-- %s\n", __func__); -} - -/* - * Clone an NFS4 server record on xdev traversal (FSID-change) - */ -static struct dentry * -nfs4_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 super_block *s; -	struct nfs_server *server; -	struct dentry *mntroot; -	int (*compare_super)(struct super_block *, void *) = nfs_compare_super; -	struct nfs_sb_mountdata sb_mntdata = { -		.mntflags = flags, -	}; -	int error; - -	dprintk("--> nfs4_xdev_mount()\n"); - -	/* create a new volume representation */ -	server = nfs_clone_server(NFS_SB(data->sb), data->fh, data->fattr); -	if (IS_ERR(server)) { -		error = PTR_ERR(server); -		goto out_err_noserver; -	} -	sb_mntdata.server = server; - -	if (server->flags & NFS4_MOUNT_UNSHARED) -		compare_super = NULL; - -	/* Get a superblock - note that we may end up sharing one that already exists */ -	s = sget(&nfs4_fs_type, compare_super, nfs_set_super, &sb_mntdata); -	if (IS_ERR(s)) { -		error = PTR_ERR(s); -		goto out_err_nosb; -	} - -	if (s->s_fs_info != server) { -		nfs_free_server(server); -		server = NULL; -	} else { -		error = nfs_bdi_register(server); -		if (error) -			goto error_splat_bdi; -	} - -	if (!s->s_root) { -		/* initial superblock/root creation */ -		nfs4_clone_super(s, data->sb); -		nfs_fscache_get_super_cookie(s, NULL, data); -	} - -	mntroot = nfs4_get_root(s, data->fh); -	if (IS_ERR(mntroot)) { -		error = PTR_ERR(mntroot); -		goto error_splat_super; -	} -	if (mntroot->d_inode->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) { -		dput(mntroot); -		error = -ESTALE; -		goto error_splat_super; -	} - -	s->s_flags |= MS_ACTIVE; - -	security_sb_clone_mnt_opts(data->sb, s); - -	dprintk("<-- nfs4_xdev_mount() = 0\n"); -	return mntroot; - -out_err_nosb: -	nfs_free_server(server); -out_err_noserver: -	dprintk("<-- nfs4_xdev_mount() = %d [error]\n", error); -	return ERR_PTR(error); - -error_splat_super: -	if (server && !s->s_root) -		bdi_unregister(&server->backing_dev_info); -error_splat_bdi: -	deactivate_locked_super(s); -	dprintk("<-- nfs4_xdev_mount() = %d [splat]\n", error); -	return ERR_PTR(error); -} - -static struct dentry * -nfs4_remote_referral_mount(struct file_system_type *fs_type, int flags, -			   const char *dev_name, void *raw_data) -{ -	struct nfs_clone_mount *data = raw_data; -	struct super_block *s; -	struct nfs_server *server; -	struct dentry *mntroot; -	struct nfs_fh *mntfh; -	int (*compare_super)(struct super_block *, void *) = nfs_compare_super; -	struct nfs_sb_mountdata sb_mntdata = { -		.mntflags = flags, -	}; -	int error = -ENOMEM; - -	dprintk("--> nfs4_referral_get_sb()\n"); - -	mntfh = nfs_alloc_fhandle(); -	if (mntfh == NULL) -		goto out_err_nofh; - -	/* create a new volume representation */ -	server = nfs4_create_referral_server(data, mntfh); -	if (IS_ERR(server)) { -		error = PTR_ERR(server); -		goto out_err_noserver; -	} -	sb_mntdata.server = server; - -	if (server->flags & NFS4_MOUNT_UNSHARED) -		compare_super = NULL; - -	/* Get a superblock - note that we may end up sharing one that already exists */ -	s = sget(&nfs4_fs_type, compare_super, nfs_set_super, &sb_mntdata); -	if (IS_ERR(s)) { -		error = PTR_ERR(s); -		goto out_err_nosb; -	} - -	if (s->s_fs_info != server) { -		nfs_free_server(server); -		server = NULL; -	} else { -		error = nfs_bdi_register(server); -		if (error) -			goto error_splat_bdi; -	} - -	if (!s->s_root) { -		/* initial superblock/root creation */ -		nfs4_fill_super(s); -		nfs_fscache_get_super_cookie(s, NULL, data); -	} - -	mntroot = nfs4_get_root(s, mntfh); -	if (IS_ERR(mntroot)) { -		error = PTR_ERR(mntroot); -		goto error_splat_super; -	} -	if (mntroot->d_inode->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) { -		dput(mntroot); -		error = -ESTALE; -		goto error_splat_super; -	} - -	s->s_flags |= MS_ACTIVE; - -	security_sb_clone_mnt_opts(data->sb, s); - -	nfs_free_fhandle(mntfh); -	dprintk("<-- nfs4_referral_get_sb() = 0\n"); -	return mntroot; - -out_err_nosb: -	nfs_free_server(server); -out_err_noserver: -	nfs_free_fhandle(mntfh); -out_err_nofh: -	dprintk("<-- nfs4_referral_get_sb() = %d [error]\n", error); -	return ERR_PTR(error); - -error_splat_super: -	if (server && !s->s_root) -		bdi_unregister(&server->backing_dev_info); -error_splat_bdi: -	deactivate_locked_super(s); -	nfs_free_fhandle(mntfh); -	dprintk("<-- nfs4_referral_get_sb() = %d [splat]\n", error); -	return ERR_PTR(error); -} - -/* - * Create an NFS4 server record on referral traversal - */ -static int nfs4_referral_get_sb(struct file_system_type *fs_type, -		int flags, const char *dev_name, void *raw_data, -		struct vfsmount *mnt) -{ -	struct nfs_clone_mount *data = raw_data; -	char *export_path; -	struct vfsmount *root_mnt; -	int error; - -	dprintk("--> nfs4_referral_get_sb()\n"); - -	export_path = data->mnt_path; -	data->mnt_path = "/"; - -	root_mnt = nfs_do_root_mount(&nfs4_remote_referral_fs_type, -			flags, data, data->hostname); -	data->mnt_path = export_path; - -	error = PTR_ERR(root_mnt); -	if (IS_ERR(root_mnt)) -		goto out; - -	error = nfs_follow_remote_path(root_mnt, export_path, mnt); -out: -	dprintk("<-- nfs4_referral_get_sb() = %d%s\n", error, -			error != 0 ? " [error]" : ""); -	return error; -}  #endif /* CONFIG_NFS_V4 */  | 
