Skip to content

Commit 99bc9f2

Browse files
neilbrownTrond Myklebust
authored andcommitted
NFS: add barriers when testing for NFS_FSDATA_BLOCKED
dentry->d_fsdata is set to NFS_FSDATA_BLOCKED while unlinking or renaming-over a file to ensure that no open succeeds while the NFS operation progressed on the server. Setting dentry->d_fsdata to NFS_FSDATA_BLOCKED is done under ->d_lock after checking the refcount is not elevated. Any attempt to open the file (through that name) will go through lookp_open() which will take ->d_lock while incrementing the refcount, we can be sure that once the new value is set, __nfs_lookup_revalidate() *will* see the new value and will block. We don't have any locking guarantee that when we set ->d_fsdata to NULL, the wait_var_event() in __nfs_lookup_revalidate() will notice. wait/wake primitives do NOT provide barriers to guarantee order. We must use smp_load_acquire() in wait_var_event() to ensure we look at an up-to-date value, and must use smp_store_release() before wake_up_var(). This patch adds those barrier functions and factors out block_revalidate() and unblock_revalidate() far clarity. There is also a hypothetical bug in that if memory allocation fails (which never happens in practice) we might leave ->d_fsdata locked. This patch adds the missing call to unblock_revalidate(). Reported-and-tested-by: Richard Kojedzinszky <[email protected]> Closes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1071501 Fixes: 3c59366 ("NFS: don't unhash dentry during unlink/rename") Signed-off-by: NeilBrown <[email protected]> Signed-off-by: Trond Myklebust <[email protected]>
1 parent 33c94d7 commit 99bc9f2

File tree

1 file changed

+32
-15
lines changed

1 file changed

+32
-15
lines changed

fs/nfs/dir.c

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,9 +1803,10 @@ __nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags,
18031803
if (parent != READ_ONCE(dentry->d_parent))
18041804
return -ECHILD;
18051805
} else {
1806-
/* Wait for unlink to complete */
1806+
/* Wait for unlink to complete - see unblock_revalidate() */
18071807
wait_var_event(&dentry->d_fsdata,
1808-
dentry->d_fsdata != NFS_FSDATA_BLOCKED);
1808+
smp_load_acquire(&dentry->d_fsdata)
1809+
!= NFS_FSDATA_BLOCKED);
18091810
parent = dget_parent(dentry);
18101811
ret = reval(d_inode(parent), dentry, flags);
18111812
dput(parent);
@@ -1818,6 +1819,29 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
18181819
return __nfs_lookup_revalidate(dentry, flags, nfs_do_lookup_revalidate);
18191820
}
18201821

1822+
static void block_revalidate(struct dentry *dentry)
1823+
{
1824+
/* old devname - just in case */
1825+
kfree(dentry->d_fsdata);
1826+
1827+
/* Any new reference that could lead to an open
1828+
* will take ->d_lock in lookup_open() -> d_lookup().
1829+
* Holding this lock ensures we cannot race with
1830+
* __nfs_lookup_revalidate() and removes and need
1831+
* for further barriers.
1832+
*/
1833+
lockdep_assert_held(&dentry->d_lock);
1834+
1835+
dentry->d_fsdata = NFS_FSDATA_BLOCKED;
1836+
}
1837+
1838+
static void unblock_revalidate(struct dentry *dentry)
1839+
{
1840+
/* store_release ensures wait_var_event() sees the update */
1841+
smp_store_release(&dentry->d_fsdata, NULL);
1842+
wake_up_var(&dentry->d_fsdata);
1843+
}
1844+
18211845
/*
18221846
* A weaker form of d_revalidate for revalidating just the d_inode(dentry)
18231847
* when we don't really care about the dentry name. This is called when a
@@ -2551,15 +2575,12 @@ int nfs_unlink(struct inode *dir, struct dentry *dentry)
25512575
spin_unlock(&dentry->d_lock);
25522576
goto out;
25532577
}
2554-
/* old devname */
2555-
kfree(dentry->d_fsdata);
2556-
dentry->d_fsdata = NFS_FSDATA_BLOCKED;
2578+
block_revalidate(dentry);
25572579

25582580
spin_unlock(&dentry->d_lock);
25592581
error = nfs_safe_remove(dentry);
25602582
nfs_dentry_remove_handle_error(dir, dentry, error);
2561-
dentry->d_fsdata = NULL;
2562-
wake_up_var(&dentry->d_fsdata);
2583+
unblock_revalidate(dentry);
25632584
out:
25642585
trace_nfs_unlink_exit(dir, dentry, error);
25652586
return error;
@@ -2666,8 +2687,7 @@ nfs_unblock_rename(struct rpc_task *task, struct nfs_renamedata *data)
26662687
{
26672688
struct dentry *new_dentry = data->new_dentry;
26682689

2669-
new_dentry->d_fsdata = NULL;
2670-
wake_up_var(&new_dentry->d_fsdata);
2690+
unblock_revalidate(new_dentry);
26712691
}
26722692

26732693
/*
@@ -2729,11 +2749,6 @@ int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
27292749
if (WARN_ON(new_dentry->d_flags & DCACHE_NFSFS_RENAMED) ||
27302750
WARN_ON(new_dentry->d_fsdata == NFS_FSDATA_BLOCKED))
27312751
goto out;
2732-
if (new_dentry->d_fsdata) {
2733-
/* old devname */
2734-
kfree(new_dentry->d_fsdata);
2735-
new_dentry->d_fsdata = NULL;
2736-
}
27372752

27382753
spin_lock(&new_dentry->d_lock);
27392754
if (d_count(new_dentry) > 2) {
@@ -2755,7 +2770,7 @@ int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
27552770
new_dentry = dentry;
27562771
new_inode = NULL;
27572772
} else {
2758-
new_dentry->d_fsdata = NFS_FSDATA_BLOCKED;
2773+
block_revalidate(new_dentry);
27592774
must_unblock = true;
27602775
spin_unlock(&new_dentry->d_lock);
27612776
}
@@ -2767,6 +2782,8 @@ int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
27672782
task = nfs_async_rename(old_dir, new_dir, old_dentry, new_dentry,
27682783
must_unblock ? nfs_unblock_rename : NULL);
27692784
if (IS_ERR(task)) {
2785+
if (must_unblock)
2786+
unblock_revalidate(new_dentry);
27702787
error = PTR_ERR(task);
27712788
goto out;
27722789
}

0 commit comments

Comments
 (0)