summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2013-08-30 12:24:25 -0400
committerTrond Myklebust <Trond.Myklebust@netapp.com>2013-09-03 15:26:29 -0400
commitba6c05928dcafc7e0a0c8e4ee6a293ba47190fd4 (patch)
tree3b32adfdc8fb978f1933d43591e36ed0b14897d6
parenta5250def7c4549a6a1cd8257900bef9c12ffc2fc (diff)
NFS: Ensure that rmdir() waits for sillyrenames to complete
If an NFS client does mkdir("dir"); fd = open("dir/file"); unlink("dir/file"); close(fd); rmdir("dir"); then the asynchronous nature of the sillyrename operation means that we can end up getting EBUSY for the rmdir() in the above test. Fix that by ensuring that we wait for any in-progress sillyrenames before sending the rmdir() to the server. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
-rw-r--r--fs/nfs/dir.c19
-rw-r--r--fs/nfs/unlink.c7
-rw-r--r--include/linux/nfs_fs.h1
3 files changed, 21 insertions, 6 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index d8149e916dd7..187caa47dad9 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1694,12 +1694,19 @@ int nfs_rmdir(struct inode *dir, struct dentry *dentry)
dir->i_sb->s_id, dir->i_ino, dentry->d_name.name);
trace_nfs_rmdir_enter(dir, dentry);
- error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name);
- /* Ensure the VFS deletes this inode */
- if (error == 0 && dentry->d_inode != NULL)
- clear_nlink(dentry->d_inode);
- else if (error == -ENOENT)
- nfs_dentry_handle_enoent(dentry);
+ if (dentry->d_inode) {
+ nfs_wait_on_sillyrename(dentry);
+ error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name);
+ /* Ensure the VFS deletes this inode */
+ switch (error) {
+ case 0:
+ clear_nlink(dentry->d_inode);
+ break;
+ case -ENOENT:
+ nfs_dentry_handle_enoent(dentry);
+ }
+ } else
+ error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name);
trace_nfs_rmdir_exit(dir, dentry, error);
return error;
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index 2c1485d18419..bb939edd4c99 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -207,6 +207,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);
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 7125cef74164..3ea4cde8701c 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -524,6 +524,7 @@ static inline void nfs4_label_free(void *label) {}
* linux/fs/nfs/unlink.c
*/
extern void nfs_complete_unlink(struct dentry *dentry, struct inode *);
+extern void nfs_wait_on_sillyrename(struct dentry *dentry);
extern void nfs_block_sillyrename(struct dentry *dentry);
extern void nfs_unblock_sillyrename(struct dentry *dentry);
extern int nfs_sillyrename(struct inode *dir, struct dentry *dentry);