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);
|
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
|
* __d_alloc - allocate a dcache entry
|
||||||
* @sb: filesystem it will belong to
|
* @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 */
|
/* test whether we have any submounts in a subdir tree */
|
||||||
extern int have_submounts(struct dentry *);
|
extern int have_submounts(struct dentry *);
|
||||||
|
extern int check_submounts_and_drop(struct dentry *);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This adds the entry to the hash queues.
|
* This adds the entry to the hash queues.
|
||||||
|
Loading…
Reference in New Issue
Block a user