|
@@ -1343,36 +1343,39 @@ void shrink_dcache_for_umount(struct super_block *sb)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static enum d_walk_ret check_and_collect(void *_data, struct dentry *dentry)
|
|
|
+struct detach_data {
|
|
|
+ struct select_data select;
|
|
|
+ struct dentry *mountpoint;
|
|
|
+};
|
|
|
+static enum d_walk_ret detach_and_collect(void *_data, struct dentry *dentry)
|
|
|
{
|
|
|
- struct select_data *data = _data;
|
|
|
+ struct detach_data *data = _data;
|
|
|
|
|
|
if (d_mountpoint(dentry)) {
|
|
|
- data->found = -EBUSY;
|
|
|
+ __dget_dlock(dentry);
|
|
|
+ data->mountpoint = dentry;
|
|
|
return D_WALK_QUIT;
|
|
|
}
|
|
|
|
|
|
- return select_collect(_data, dentry);
|
|
|
+ return select_collect(&data->select, dentry);
|
|
|
}
|
|
|
|
|
|
static void check_and_drop(void *_data)
|
|
|
{
|
|
|
- struct select_data *data = _data;
|
|
|
+ struct detach_data *data = _data;
|
|
|
|
|
|
- if (d_mountpoint(data->start))
|
|
|
- data->found = -EBUSY;
|
|
|
- if (!data->found)
|
|
|
- __d_drop(data->start);
|
|
|
+ if (!data->mountpoint && !data->select.found)
|
|
|
+ __d_drop(data->select.start);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * check_submounts_and_drop - prune dcache, check for submounts and drop
|
|
|
+ * check_submounts_and_drop - detach submounts, prune dcache, 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.
|
|
|
+ * The final d_drop is done as an atomic operation relative to
|
|
|
+ * rename_lock ensuring there are no races with d_set_mounted. This
|
|
|
+ * ensures there are no unhashed dentries on the path to a mountpoint.
|
|
|
*
|
|
|
- * @dentry: dentry to prune and drop
|
|
|
+ * @dentry: dentry to detach, prune and drop
|
|
|
*/
|
|
|
int check_submounts_and_drop(struct dentry *dentry)
|
|
|
{
|
|
@@ -1385,19 +1388,24 @@ int check_submounts_and_drop(struct dentry *dentry)
|
|
|
}
|
|
|
|
|
|
for (;;) {
|
|
|
- struct select_data data;
|
|
|
+ struct detach_data data;
|
|
|
|
|
|
- INIT_LIST_HEAD(&data.dispose);
|
|
|
- data.start = dentry;
|
|
|
- data.found = 0;
|
|
|
+ data.mountpoint = NULL;
|
|
|
+ INIT_LIST_HEAD(&data.select.dispose);
|
|
|
+ data.select.start = dentry;
|
|
|
+ data.select.found = 0;
|
|
|
+
|
|
|
+ d_walk(dentry, &data, detach_and_collect, check_and_drop);
|
|
|
|
|
|
- d_walk(dentry, &data, check_and_collect, check_and_drop);
|
|
|
- ret = data.found;
|
|
|
+ if (data.select.found)
|
|
|
+ shrink_dentry_list(&data.select.dispose);
|
|
|
|
|
|
- if (!list_empty(&data.dispose))
|
|
|
- shrink_dentry_list(&data.dispose);
|
|
|
+ if (data.mountpoint) {
|
|
|
+ detach_mounts(data.mountpoint);
|
|
|
+ dput(data.mountpoint);
|
|
|
+ }
|
|
|
|
|
|
- if (ret <= 0)
|
|
|
+ if (!data.mountpoint && !data.select.found)
|
|
|
break;
|
|
|
|
|
|
cond_resched();
|
|
@@ -2639,10 +2647,8 @@ static struct dentry *__d_unalias(struct inode *inode,
|
|
|
goto out_err;
|
|
|
m2 = &alias->d_parent->d_inode->i_mutex;
|
|
|
out_unalias:
|
|
|
- if (likely(!d_mountpoint(alias))) {
|
|
|
- __d_move(alias, dentry, false);
|
|
|
- ret = alias;
|
|
|
- }
|
|
|
+ __d_move(alias, dentry, false);
|
|
|
+ ret = alias;
|
|
|
out_err:
|
|
|
spin_unlock(&inode->i_lock);
|
|
|
if (m2)
|