@@ -58,6 +58,7 @@ struct mount {
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
+ struct list_head mnt_umounts; /* list of children that are being unmounted */
#ifdef CONFIG_FSNOTIFY
struct hlist_head mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
@@ -237,6 +237,7 @@ static struct mount *alloc_vfsmnt(const char *name)
INIT_LIST_HEAD(&mnt->mnt_slave_list);
INIT_LIST_HEAD(&mnt->mnt_slave);
INIT_HLIST_NODE(&mnt->mnt_mp_list);
+ INIT_LIST_HEAD(&mnt->mnt_umounts);
#ifdef CONFIG_FSNOTIFY
INIT_HLIST_HEAD(&mnt->mnt_fsnotify_marks);
#endif
@@ -650,13 +651,11 @@ struct mount *__lookup_mnt_last(struct vfsmount *mnt, struct dentry *dentry)
p = __lookup_mnt(mnt, dentry);
if (!p)
goto out;
- if (!(p->mnt.mnt_flags & MNT_UMOUNT))
- res = p;
+ res = p;
hlist_for_each_entry_continue(p, mnt_hash) {
if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)
break;
- if (!(p->mnt.mnt_flags & MNT_UMOUNT))
- res = p;
+ res = p;
}
out:
return res;
@@ -134,7 +134,8 @@ void change_mnt_propagation(struct mount *mnt, int type)
}
/*
- * get the next mount in the propagation tree.
+ * get the next mount that is not a slave of the current mount in the
+ * propagation tree.
* @m: the mount seen last
* @origin: the original mount from where the tree walk initiated
*
@@ -143,13 +144,9 @@ void change_mnt_propagation(struct mount *mnt, int type)
* vfsmount found while iterating with propagation_next() is
* a peer of one we'd found earlier.
*/
-static struct mount *propagation_next(struct mount *m,
- struct mount *origin)
+static struct mount *propagation_next_sib(struct mount *m,
+ struct mount *origin)
{
- /* are there any slaves of this mount? */
- if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
- return first_slave(m);
-
while (1) {
struct mount *master = m->mnt_master;
@@ -164,6 +161,26 @@ static struct mount *propagation_next(struct mount *m,
}
}
+/*
+ * get the next mount in the propagation tree.
+ * @m: the mount seen last
+ * @origin: the original mount from where the tree walk initiated
+ *
+ * Note that peer groups form contiguous segments of slave lists.
+ * We rely on that in get_source() to be able to find out if
+ * vfsmount found while iterating with propagation_next() is
+ * a peer of one we'd found earlier.
+ */
+static struct mount *propagation_next(struct mount *m,
+ struct mount *origin)
+{
+ /* are there any slaves of this mount? */
+ if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+ return first_slave(m);
+
+ return propagation_next_sib(m, origin);
+}
+
static struct mount *next_group(struct mount *m, struct mount *origin)
{
while (1) {
@@ -389,57 +406,92 @@ void propagate_mount_unlock(struct mount *mnt)
}
}
-/*
- * Mark all mounts that the MNT_LOCKED logic will allow to be unmounted.
- */
-static void mark_umount_candidates(struct mount *mnt)
+static struct mount *propagation_visit_child(struct mount *last_child,
+ struct mount *origin_child)
{
- struct mount *parent = mnt->mnt_parent;
- struct mount *m;
+ struct mount *m = last_child->mnt_parent;
+ struct mount *origin = origin_child->mnt_parent;
+ struct dentry *mountpoint = origin_child->mnt_mountpoint;
+ struct mount *child;
- BUG_ON(parent == mnt);
+ /* Has this part of the propgation tree already been visited? */
+ if (IS_MNT_MARKED(last_child))
+ return NULL;
- for (m = propagation_next(parent, parent); m;
- m = propagation_next(m, parent)) {
- struct mount *child = __lookup_mnt_last(&m->mnt,
- mnt->mnt_mountpoint);
- if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
- SET_MNT_MARK(child);
- }
+ SET_MNT_MARK(last_child);
+
+ m = propagation_next(m, origin);
+ while (m) {
+ child = __lookup_mnt_last(&m->mnt, mountpoint);
+ if (child && !IS_MNT_MARKED(child))
+ return child;
+
+ if (!child)
+ m = propagation_next(m, origin);
+ else
+ m = propagation_next_sib(m, origin);
}
+ return NULL;
}
-/*
- * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
- * parent propagates to.
- */
-static void __propagate_umount(struct mount *mnt)
+static struct mount *propagation_revisit_child(struct mount *last_child,
+ struct mount *origin_child)
{
- struct mount *parent = mnt->mnt_parent;
- struct mount *m;
+ struct mount *m = last_child->mnt_parent;
+ struct mount *origin = origin_child->mnt_parent;
+ struct dentry *mountpoint = origin_child->mnt_mountpoint;
+ struct mount *child;
- BUG_ON(parent == mnt);
+ /* Has this part of the propgation tree already been revisited? */
+ if (!IS_MNT_MARKED(last_child))
+ return NULL;
- for (m = propagation_next(parent, parent); m;
- m = propagation_next(m, parent)) {
+ CLEAR_MNT_MARK(last_child);
- struct mount *child = __lookup_mnt_last(&m->mnt,
- mnt->mnt_mountpoint);
- /*
- * umount the child only if the child has no children
- * and the child is marked safe to unmount.
- */
- if (!child || !IS_MNT_MARKED(child))
- continue;
- CLEAR_MNT_MARK(child);
- if (list_empty(&child->mnt_mounts)) {
- list_del_init(&child->mnt_child);
- child->mnt.mnt_flags |= MNT_UMOUNT;
- list_move_tail(&child->mnt_list, &mnt->mnt_list);
- }
+ m = propagation_next(m, origin);
+ while (m) {
+ child = __lookup_mnt_last(&m->mnt, mountpoint);
+ if (child && IS_MNT_MARKED(child))
+ return child;
+
+ if (!child)
+ m = propagation_next(m, origin);
+ else
+ m = propagation_next_sib(m, origin);
}
+ return NULL;
}
+static void start_umount_propagation(struct mount *child,
+ struct list_head *to_umount)
+{
+ do {
+ struct mount *parent = child->mnt_parent;
+
+ if ((child->mnt.mnt_flags & MNT_UMOUNT) ||
+ !list_empty(&child->mnt_mounts))
+ return;
+
+ if (!IS_MNT_LOCKED(child))
+ list_move_tail(&child->mnt_child, to_umount);
+ else
+ list_move_tail(&child->mnt_child, &parent->mnt_umounts);
+
+ child = NULL;
+ if (IS_MNT_MARKED(parent))
+ child = parent;
+ } while (child);
+}
+
+static void end_umount_propagation(struct mount *child)
+{
+ struct mount *parent = child->mnt_parent;
+
+ if (!list_empty(&parent->mnt_umounts))
+ list_splice_tail_init(&parent->mnt_umounts, &parent->mnt_mounts);
+}
+
+
/*
* collect all mounts that receive propagation from the mount in @list,
* and return these additional mounts in the same list.
@@ -447,14 +499,39 @@ static void __propagate_umount(struct mount *mnt)
*
* vfsmount lock must be held for write
*/
-int propagate_umount(struct list_head *list)
+void propagate_umount(struct list_head *list)
{
struct mount *mnt;
+ LIST_HEAD(to_umount);
+ LIST_HEAD(tmp_list);
+
+ /* Find candidates for unmounting */
+ list_for_each_entry(mnt, list, mnt_list) {
+ struct mount *child;
+ for (child = propagation_visit_child(mnt, mnt); child;
+ child = propagation_visit_child(child, mnt))
+ start_umount_propagation(child, &to_umount);
+ }
- list_for_each_entry_reverse(mnt, list, mnt_list)
- mark_umount_candidates(mnt);
+ /* Begin unmounting */
+ while (!list_empty(&to_umount)) {
+ mnt = list_first_entry(&to_umount, struct mount, mnt_child);
- list_for_each_entry(mnt, list, mnt_list)
- __propagate_umount(mnt);
- return 0;
+ list_del_init(&mnt->mnt_child);
+ mnt->mnt.mnt_flags |= MNT_UMOUNT;
+ list_move_tail(&mnt->mnt_list, &tmp_list);
+
+ if (!list_empty(&mnt->mnt_umounts))
+ list_splice_tail_init(&mnt->mnt_umounts, &to_umount);
+ }
+
+ /* Cleanup the mount propagation tree */
+ list_for_each_entry(mnt, list, mnt_list) {
+ struct mount *child;
+ for (child = propagation_revisit_child(mnt, mnt); child;
+ child = propagation_revisit_child(child, mnt))
+ end_umount_propagation(child);
+ }
+
+ list_splice_tail(&tmp_list, list);
}
@@ -41,7 +41,7 @@ static inline void set_mnt_shared(struct mount *mnt)
void change_mnt_propagation(struct mount *, int);
int propagate_mnt(struct mount *, struct mountpoint *, struct mount *,
struct hlist_head *);
-int propagate_umount(struct list_head *);
+void propagate_umount(struct list_head *);
int propagate_mount_busy(struct mount *, int);
void propagate_mount_unlock(struct mount *);
void mnt_release_group_id(struct mount *);