diff options
Diffstat (limited to 'fs/nfs/unlink.c')
| -rw-r--r-- | fs/nfs/unlink.c | 200 | 
1 files changed, 122 insertions, 78 deletions
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c index 7bdec853140..de54129336c 100644 --- a/fs/nfs/unlink.c +++ b/fs/nfs/unlink.c @@ -14,20 +14,14 @@  #include <linux/sched.h>  #include <linux/wait.h>  #include <linux/namei.h> +#include <linux/fsnotify.h>  #include "internal.h"  #include "nfs4_fs.h"  #include "iostat.h"  #include "delegation.h" -struct nfs_unlinkdata { -	struct hlist_node list; -	struct nfs_removeargs args; -	struct nfs_removeres res; -	struct inode *dir; -	struct rpc_cred	*cred; -	struct nfs_fattr dir_attr; -}; +#include "nfstrace.h"  /**   * nfs_free_unlinkdata - release data from a sillydelete operation. @@ -86,8 +80,9 @@ static void nfs_async_unlink_done(struct rpc_task *task, void *calldata)  	struct nfs_unlinkdata *data = calldata;  	struct inode *dir = data->dir; +	trace_nfs_sillyrename_unlink(data, task->tk_status);  	if (!NFS_PROTO(dir)->unlink_done(task, dir)) -		nfs_restart_rpc(task, NFS_SERVER(dir)->nfs_client); +		rpc_restart_call_prepare(task);  }  /** @@ -107,25 +102,16 @@ static void nfs_async_unlink_release(void *calldata)  	nfs_sb_deactive(sb);  } -#if defined(CONFIG_NFS_V4_1) -void nfs_unlink_prepare(struct rpc_task *task, void *calldata) +static void nfs_unlink_prepare(struct rpc_task *task, void *calldata)  {  	struct nfs_unlinkdata *data = calldata; -	struct nfs_server *server = NFS_SERVER(data->dir); - -	if (nfs4_setup_sequence(server, &data->args.seq_args, -				&data->res.seq_res, 1, task)) -		return; -	rpc_call_start(task); +	NFS_PROTO(data->dir)->unlink_rpc_prepare(task, data);  } -#endif /* CONFIG_NFS_V4_1 */  static const struct rpc_call_ops nfs_unlink_ops = {  	.rpc_call_done = nfs_async_unlink_done,  	.rpc_release = nfs_async_unlink_release, -#if defined(CONFIG_NFS_V4_1)  	.rpc_call_prepare = nfs_unlink_prepare, -#endif /* CONFIG_NFS_V4_1 */  };  static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct nfs_unlinkdata *data) @@ -147,23 +133,33 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n  	alias = d_lookup(parent, &data->args.name);  	if (alias != NULL) { -		int ret = 0; +		int ret; +		void *devname_garbage = NULL;  		/*  		 * Hey, we raced with lookup... See if we need to transfer  		 * the sillyrename information to the aliased dentry.  		 */  		nfs_free_dname(data); +		ret = nfs_copy_dname(alias, data);  		spin_lock(&alias->d_lock); -		if (alias->d_inode != NULL && +		if (ret == 0 && alias->d_inode != NULL &&  		    !(alias->d_flags & DCACHE_NFSFS_RENAMED)) { +			devname_garbage = alias->d_fsdata;  			alias->d_fsdata = data;  			alias->d_flags |= DCACHE_NFSFS_RENAMED;  			ret = 1; -		} +		} else +			ret = 0;  		spin_unlock(&alias->d_lock);  		nfs_dec_sillycount(dir);  		dput(alias); +		/* +		 * If we'd displaced old cached devname, free it.  At that +		 * point dentry is definitely not a root, so we won't need +		 * that anymore. +		 */ +		kfree(devname_garbage);  		return ret;  	}  	data->dir = igrab(dir); @@ -180,7 +176,7 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n  	task_setup_data.rpc_client = NFS_CLIENT(dir);  	task = rpc_run_task(&task_setup_data);  	if (!IS_ERR(task)) -		rpc_put_task(task); +		rpc_put_task_async(task);  	return 1;  } @@ -195,8 +191,6 @@ static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data)  	if (parent == NULL)  		goto out_free;  	dir = parent->d_inode; -	if (nfs_copy_dname(dentry, data) != 0) -		goto out_dput;  	/* Non-exclusive lock protects against concurrent lookup() calls */  	spin_lock(&dir->i_lock);  	if (atomic_inc_not_zero(&NFS_I(dir)->silly_count) == 0) { @@ -214,6 +208,13 @@ out_free:  	return ret;  } +void nfs_wait_on_sillyrename(struct dentry *dentry) +{ +	struct nfs_inode *nfsi = NFS_I(dentry->d_inode); + +	wait_event(nfsi->waitqueue, atomic_read(&nfsi->silly_count) <= 1); +} +  void nfs_block_sillyrename(struct dentry *dentry)  {  	struct nfs_inode *nfsi = NFS_I(dentry->d_inode); @@ -252,6 +253,7 @@ nfs_async_unlink(struct inode *dir, struct dentry *dentry)  {  	struct nfs_unlinkdata *data;  	int status = -ENOMEM; +	void *devname_garbage = NULL;  	data = kzalloc(sizeof(*data), GFP_KERNEL);  	if (data == NULL) @@ -269,8 +271,15 @@ nfs_async_unlink(struct inode *dir, struct dentry *dentry)  	if (dentry->d_flags & DCACHE_NFSFS_RENAMED)  		goto out_unlock;  	dentry->d_flags |= DCACHE_NFSFS_RENAMED; +	devname_garbage = dentry->d_fsdata;  	dentry->d_fsdata = data;  	spin_unlock(&dentry->d_lock); +	/* +	 * If we'd displaced old cached devname, free it.  At that +	 * point dentry is definitely not a root, so we won't need +	 * that anymore. +	 */ +	kfree(devname_garbage);  	return 0;  out_unlock:  	spin_unlock(&dentry->d_lock); @@ -299,6 +308,7 @@ nfs_complete_unlink(struct dentry *dentry, struct inode *inode)  	if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {  		dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;  		data = dentry->d_fsdata; +		dentry->d_fsdata = NULL;  	}  	spin_unlock(&dentry->d_lock); @@ -315,6 +325,7 @@ nfs_cancel_async_unlink(struct dentry *dentry)  		struct nfs_unlinkdata *data = dentry->d_fsdata;  		dentry->d_flags &= ~DCACHE_NFSFS_RENAMED; +		dentry->d_fsdata = NULL;  		spin_unlock(&dentry->d_lock);  		nfs_free_unlinkdata(data);  		return; @@ -322,18 +333,6 @@ nfs_cancel_async_unlink(struct dentry *dentry)  	spin_unlock(&dentry->d_lock);  } -struct nfs_renamedata { -	struct nfs_renameargs	args; -	struct nfs_renameres	res; -	struct rpc_cred		*cred; -	struct inode		*old_dir; -	struct dentry		*old_dentry; -	struct nfs_fattr	old_fattr; -	struct inode		*new_dir; -	struct dentry		*new_dentry; -	struct nfs_fattr	new_fattr; -}; -  /**   * nfs_async_rename_done - Sillyrename post-processing   * @task: rpc_task of the sillyrename @@ -346,19 +345,17 @@ static void nfs_async_rename_done(struct rpc_task *task, void *calldata)  	struct nfs_renamedata *data = calldata;  	struct inode *old_dir = data->old_dir;  	struct inode *new_dir = data->new_dir; +	struct dentry *old_dentry = data->old_dentry; +	trace_nfs_sillyrename_rename(old_dir, old_dentry, +			new_dir, data->new_dentry, task->tk_status);  	if (!NFS_PROTO(old_dir)->rename_done(task, old_dir, new_dir)) { -		nfs_restart_rpc(task, NFS_SERVER(old_dir)->nfs_client); +		rpc_restart_call_prepare(task);  		return;  	} -	if (task->tk_status != 0) { -		nfs_cancel_async_unlink(data->old_dentry); -		return; -	} - -	nfs_set_verifier(data->old_dentry, nfs_save_change_attribute(old_dir)); -	d_move(data->old_dentry, data->new_dentry); +	if (data->complete) +		data->complete(task, data);  }  /** @@ -382,25 +379,16 @@ static void nfs_async_rename_release(void *calldata)  	kfree(data);  } -#if defined(CONFIG_NFS_V4_1)  static void nfs_rename_prepare(struct rpc_task *task, void *calldata)  {  	struct nfs_renamedata *data = calldata; -	struct nfs_server *server = NFS_SERVER(data->old_dir); - -	if (nfs4_setup_sequence(server, &data->args.seq_args, -				&data->res.seq_res, 1, task)) -		return; -	rpc_call_start(task); +	NFS_PROTO(data->old_dir)->rename_rpc_prepare(task, data);  } -#endif /* CONFIG_NFS_V4_1 */  static const struct rpc_call_ops nfs_rename_ops = {  	.rpc_call_done = nfs_async_rename_done,  	.rpc_release = nfs_async_rename_release, -#if defined(CONFIG_NFS_V4_1)  	.rpc_call_prepare = nfs_rename_prepare, -#endif /* CONFIG_NFS_V4_1 */  };  /** @@ -412,9 +400,10 @@ static const struct rpc_call_ops nfs_rename_ops = {   *   * It's expected that valid references to the dentries and inodes are held   */ -static struct rpc_task * +struct rpc_task *  nfs_async_rename(struct inode *old_dir, struct inode *new_dir, -		 struct dentry *old_dentry, struct dentry *new_dentry) +		 struct dentry *old_dentry, struct dentry *new_dentry, +		 void (*complete)(struct rpc_task *, struct nfs_renamedata *))  {  	struct nfs_renamedata *data;  	struct rpc_message msg = { }; @@ -429,7 +418,7 @@ nfs_async_rename(struct inode *old_dir, struct inode *new_dir,  	data = kzalloc(sizeof(*data), GFP_KERNEL);  	if (data == NULL)  		return ERR_PTR(-ENOMEM); -	task_setup_data.callback_data = data, +	task_setup_data.callback_data = data;  	data->cred = rpc_lookup_cred();  	if (IS_ERR(data->cred)) { @@ -451,6 +440,7 @@ nfs_async_rename(struct inode *old_dir, struct inode *new_dir,  	data->new_dentry = dget(new_dentry);  	nfs_fattr_init(&data->old_fattr);  	nfs_fattr_init(&data->new_fattr); +	data->complete = complete;  	/* set up nfs_renameargs */  	data->args.old_dir = NFS_FH(old_dir); @@ -469,6 +459,35 @@ nfs_async_rename(struct inode *old_dir, struct inode *new_dir,  	return rpc_run_task(&task_setup_data);  } +/* + * Perform tasks needed when a sillyrename is done such as cancelling the + * queued async unlink if it failed. + */ +static void +nfs_complete_sillyrename(struct rpc_task *task, struct nfs_renamedata *data) +{ +	struct dentry *dentry = data->old_dentry; + +	if (task->tk_status != 0) { +		nfs_cancel_async_unlink(dentry); +		return; +	} + +	/* +	 * vfs_unlink and the like do not issue this when a file is +	 * sillyrenamed, so do it here. +	 */ +	fsnotify_nameremove(dentry, 0); +} + +#define SILLYNAME_PREFIX ".nfs" +#define SILLYNAME_PREFIX_LEN ((unsigned)sizeof(SILLYNAME_PREFIX) - 1) +#define SILLYNAME_FILEID_LEN ((unsigned)sizeof(u64) << 1) +#define SILLYNAME_COUNTER_LEN ((unsigned)sizeof(unsigned int) << 1) +#define SILLYNAME_LEN (SILLYNAME_PREFIX_LEN + \ +		SILLYNAME_FILEID_LEN + \ +		SILLYNAME_COUNTER_LEN) +  /**   * nfs_sillyrename - Perform a silly-rename of a dentry   * @dir: inode of directory that contains dentry @@ -481,48 +500,52 @@ nfs_async_rename(struct inode *old_dir, struct inode *new_dir,   * and only performs the unlink once the last reference to it is put.   *   * The final cleanup is done during dentry_iput. + * + * (Note: NFSv4 is stateful, and has opens, so in theory an NFSv4 server + * could take responsibility for keeping open files referenced.  The server + * would also need to ensure that opened-but-deleted files were kept over + * reboots.  However, we may not assume a server does so.  (RFC 5661 + * does provide an OPEN4_RESULT_PRESERVE_UNLINKED flag that a server can + * use to advertise that it does this; some day we may take advantage of + * it.))   */  int  nfs_sillyrename(struct inode *dir, struct dentry *dentry)  {  	static unsigned int sillycounter; -	const int      fileidsize  = sizeof(NFS_FILEID(dentry->d_inode))*2; -	const int      countersize = sizeof(sillycounter)*2; -	const int      slen        = sizeof(".nfs")+fileidsize+countersize-1; -	char           silly[slen+1]; +	unsigned char silly[SILLYNAME_LEN + 1]; +	unsigned long long fileid;  	struct dentry *sdentry;  	struct rpc_task *task; -	int            error = -EIO; +	int            error = -EBUSY; -	dfprintk(VFS, "NFS: silly-rename(%s/%s, ct=%d)\n", -		dentry->d_parent->d_name.name, dentry->d_name.name, -		atomic_read(&dentry->d_count)); +	dfprintk(VFS, "NFS: silly-rename(%pd2, ct=%d)\n", +		dentry, d_count(dentry));  	nfs_inc_stats(dir, NFSIOS_SILLYRENAME);  	/*  	 * We don't allow a dentry to be silly-renamed twice.  	 */ -	error = -EBUSY;  	if (dentry->d_flags & DCACHE_NFSFS_RENAMED)  		goto out; -	sprintf(silly, ".nfs%*.*Lx", -		fileidsize, fileidsize, -		(unsigned long long)NFS_FILEID(dentry->d_inode)); +	fileid = NFS_FILEID(dentry->d_inode);  	/* Return delegation in anticipation of the rename */ -	nfs_inode_return_delegation(dentry->d_inode); +	NFS_PROTO(dentry->d_inode)->return_delegation(dentry->d_inode);  	sdentry = NULL;  	do { -		char *suffix = silly + slen - countersize; - +		int slen;  		dput(sdentry);  		sillycounter++; -		sprintf(suffix, "%*.*x", countersize, countersize, sillycounter); +		slen = scnprintf(silly, sizeof(silly), +				SILLYNAME_PREFIX "%0*llx%0*x", +				SILLYNAME_FILEID_LEN, fileid, +				SILLYNAME_COUNTER_LEN, sillycounter); -		dfprintk(VFS, "NFS: trying to rename %s to %s\n", -				dentry->d_name.name, silly); +		dfprintk(VFS, "NFS: trying to rename %pd to %s\n", +				dentry, silly);  		sdentry = lookup_one_len(silly, dentry->d_parent, slen);  		/* @@ -540,8 +563,17 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry)  	if (error)  		goto out_dput; +	/* populate unlinkdata with the right dname */ +	error = nfs_copy_dname(sdentry, +				(struct nfs_unlinkdata *)dentry->d_fsdata); +	if (error) { +		nfs_cancel_async_unlink(dentry); +		goto out_dput; +	} +  	/* run the rename task, undo unlink if it fails */ -	task = nfs_async_rename(dir, dir, dentry, sdentry); +	task = nfs_async_rename(dir, dir, dentry, sdentry, +					nfs_complete_sillyrename);  	if (IS_ERR(task)) {  		error = -EBUSY;  		nfs_cancel_async_unlink(dentry); @@ -552,6 +584,18 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry)  	error = rpc_wait_for_completion_task(task);  	if (error == 0)  		error = task->tk_status; +	switch (error) { +	case 0: +		/* The rename succeeded */ +		nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); +		d_move(dentry, sdentry); +		break; +	case -ERESTARTSYS: +		/* The result of the rename is unknown. Play it safe by +		 * forcing a new lookup */ +		d_drop(dentry); +		d_drop(sdentry); +	}  	rpc_put_task(task);  out_dput:  	dput(sdentry);  | 
