diff options
Diffstat (limited to 'fs/nfs/nfs4super.c')
| -rw-r--r-- | fs/nfs/nfs4super.c | 358 | 
1 files changed, 358 insertions, 0 deletions
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c new file mode 100644 index 00000000000..6f340f02f2b --- /dev/null +++ b/fs/nfs/nfs4super.c @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2012 Bryan Schumaker <bjschuma@netapp.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/nfs_idmap.h> +#include <linux/nfs4_mount.h> +#include <linux/nfs_fs.h> +#include "delegation.h" +#include "internal.h" +#include "nfs4_fs.h" +#include "dns_resolve.h" +#include "pnfs.h" +#include "nfs.h" + +#define NFSDBG_FACILITY		NFSDBG_VFS + +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, +	.destroy_inode	= nfs_destroy_inode, +	.write_inode	= nfs4_write_inode, +	.drop_inode	= nfs_drop_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_devname	= nfs_show_devname, +	.show_path	= nfs_show_path, +	.show_stats	= nfs_show_stats, +	.remount_fs	= 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, +}; + +static int nfs4_write_inode(struct inode *inode, struct writeback_control *wbc) +{ +	int ret = nfs_write_inode(inode, wbc); + +	if (ret == 0) +		ret = pnfs_layoutcommit_inode(inode, +				wbc->sync_mode == WB_SYNC_ALL); +	return ret; +} + +/* + * Clean out any remaining NFSv4 state that might be left over due + * to open() calls that passed nfs_atomic_lookup, but failed to call + * nfs_open(). + */ +static void nfs4_evict_inode(struct inode *inode) +{ +	truncate_inode_pages_final(&inode->i_data); +	clear_inode(inode); +	pnfs_return_layout(inode); +	pnfs_destroy_layout(NFS_I(inode)); +	/* If we are holding a delegation, return it! */ +	nfs_inode_return_delegation_noreclaim(inode); +	/* First call standard NFS clear_inode() code */ +	nfs_clear_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) +{ +	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; + +	/* 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); + +out: +	return mntroot; +} + +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) + 5; +	root_devname = kmalloc(len, GFP_KERNEL); +	if (root_devname == NULL) +		return ERR_PTR(-ENOMEM); +	/* 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); +	return root_mnt; +} + +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) +{ +	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 struct dentry *nfs_follow_remote_path(struct vfsmount *root_mnt, +		const char *export_path) +{ +	struct dentry *dentry; +	int err; + +	if (IS_ERR(root_mnt)) +		return ERR_CAST(root_mnt); + +	err = nfs_referral_loop_protect(); +	if (err) { +		mntput(root_mnt); +		return ERR_PTR(err); +	} + +	dentry = mount_subtree(root_mnt, export_path); +	nfs_referral_loop_unprotect(); + +	return dentry; +} + +struct dentry *nfs4_try_mount(int flags, const char *dev_name, +			      struct nfs_mount_info *mount_info, +			      struct nfs_subversion *nfs_mod) +{ +	char *export_path; +	struct vfsmount *root_mnt; +	struct dentry *res; +	struct nfs_parsed_mount_data *data = mount_info->parsed; + +	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, mount_info, +			data->nfs_server.hostname); +	data->nfs_server.export_path = export_path; + +	res = nfs_follow_remote_path(root_mnt, export_path); + +	dfprintk(MOUNT, "<-- nfs4_try_mount() = %d%s\n", +		 PTR_ERR_OR_ZERO(res), +		 IS_ERR(res) ? " [error]" : ""); +	return res; +} + +static struct dentry * +nfs4_remote_referral_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_clone_sb_security, +		.cloned = raw_data, +	}; +	struct nfs_server *server; +	struct dentry *mntroot = ERR_PTR(-ENOMEM); + +	dprintk("--> nfs4_referral_get_sb()\n"); + +	mount_info.mntfh = nfs_alloc_fhandle(); +	if (mount_info.cloned == NULL || mount_info.mntfh == NULL) +		goto out; + +	/* 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; +	} + +	mntroot = nfs_fs_mount_common(server, flags, dev_name, &mount_info, &nfs_v4); +out: +	nfs_free_fhandle(mount_info.mntfh); +	return mntroot; +} + +/* + * 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) +{ +	struct nfs_clone_mount *data = raw_data; +	char *export_path; +	struct vfsmount *root_mnt; +	struct dentry *res; + +	dprintk("--> nfs4_referral_mount()\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; + +	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; +} + + +static int __init init_nfs_v4(void) +{ +	int err; + +	err = nfs_dns_resolver_init(); +	if (err) +		goto out; + +	err = nfs_idmap_init(); +	if (err) +		goto out1; + +	err = nfs4_register_sysctl(); +	if (err) +		goto out2; + +	register_nfs_version(&nfs_v4); +	return 0; +out2: +	nfs_idmap_quit(); +out1: +	nfs_dns_resolver_destroy(); +out: +	return err; +} + +static void __exit exit_nfs_v4(void) +{ +	unregister_nfs_version(&nfs_v4); +	nfs4_unregister_sysctl(); +	nfs_idmap_quit(); +	nfs_dns_resolver_destroy(); +} + +MODULE_LICENSE("GPL"); + +module_init(init_nfs_v4); +module_exit(exit_nfs_v4);  | 
