forked from Minki/linux
vfs: check submounts and drop atomically
We check submounts before doing d_drop() on a non-empty directory dentry in NFS (have_submounts()), but we do not exclude a racing mount. Process A: have_submounts() -> returns false Process B: mount() -> success Process A: d_drop() This patch prepares the ground for the fix by doing the following operations all under the same rename lock: have_submounts() shrink_dcache_parent() d_drop() This is actually an optimization since have_submounts() and shrink_dcache_parent() both traverse the same dentry tree separately. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> CC: David Howells <dhowells@redhat.com> CC: Steven Whitehouse <swhiteho@redhat.com> CC: Trond Myklebust <Trond.Myklebust@netapp.com> CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
parent
db14fc3abc
commit
848ac114e8
65
fs/dcache.c
65
fs/dcache.c
@ -1264,6 +1264,71 @@ void shrink_dcache_parent(struct dentry *parent)
|
||||
}
|
||||
EXPORT_SYMBOL(shrink_dcache_parent);
|
||||
|
||||
static enum d_walk_ret check_and_collect(void *_data, struct dentry *dentry)
|
||||
{
|
||||
struct select_data *data = _data;
|
||||
|
||||
if (d_mountpoint(dentry)) {
|
||||
data->found = -EBUSY;
|
||||
return D_WALK_QUIT;
|
||||
}
|
||||
|
||||
return select_collect(_data, dentry);
|
||||
}
|
||||
|
||||
static void check_and_drop(void *_data)
|
||||
{
|
||||
struct select_data *data = _data;
|
||||
|
||||
if (d_mountpoint(data->start))
|
||||
data->found = -EBUSY;
|
||||
if (!data->found)
|
||||
__d_drop(data->start);
|
||||
}
|
||||
|
||||
/**
|
||||
* check_submounts_and_drop - prune dcache, check for submounts and drop
|
||||
*
|
||||
* All done as a single atomic operation relative to has_unlinked_ancestor().
|
||||
* Returns 0 if successfully unhashed @parent. If there were submounts then
|
||||
* return -EBUSY.
|
||||
*
|
||||
* @dentry: dentry to prune and drop
|
||||
*/
|
||||
int check_submounts_and_drop(struct dentry *dentry)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* Negative dentries can be dropped without further checks */
|
||||
if (!dentry->d_inode) {
|
||||
d_drop(dentry);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
struct select_data data;
|
||||
|
||||
INIT_LIST_HEAD(&data.dispose);
|
||||
data.start = dentry;
|
||||
data.found = 0;
|
||||
|
||||
d_walk(dentry, &data, check_and_collect, check_and_drop);
|
||||
ret = data.found;
|
||||
|
||||
if (!list_empty(&data.dispose))
|
||||
shrink_dentry_list(&data.dispose);
|
||||
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(check_submounts_and_drop);
|
||||
|
||||
/**
|
||||
* __d_alloc - allocate a dcache entry
|
||||
* @sb: filesystem it will belong to
|
||||
|
@ -253,6 +253,7 @@ extern void d_prune_aliases(struct inode *);
|
||||
|
||||
/* test whether we have any submounts in a subdir tree */
|
||||
extern int have_submounts(struct dentry *);
|
||||
extern int check_submounts_and_drop(struct dentry *);
|
||||
|
||||
/*
|
||||
* This adds the entry to the hash queues.
|
||||
|
Loading…
Reference in New Issue
Block a user