|
@@ -164,46 +164,94 @@ static struct mount *propagation_next(struct mount *m,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * return the source mount to be used for cloning
|
|
|
- *
|
|
|
- * @dest the current destination mount
|
|
|
- * @last_dest the last seen destination mount
|
|
|
- * @last_src the last seen source mount
|
|
|
- * @type return CL_SLAVE if the new mount has to be
|
|
|
- * cloned as a slave.
|
|
|
- */
|
|
|
-static struct mount *get_source(struct mount *dest,
|
|
|
- struct mount *last_dest,
|
|
|
- struct mount *last_src,
|
|
|
- int *type)
|
|
|
+static struct mount *next_group(struct mount *m, struct mount *origin)
|
|
|
{
|
|
|
- struct mount *p_last_src = NULL;
|
|
|
- struct mount *p_last_dest = NULL;
|
|
|
-
|
|
|
- while (last_dest != dest->mnt_master) {
|
|
|
- p_last_dest = last_dest;
|
|
|
- p_last_src = last_src;
|
|
|
- last_dest = last_dest->mnt_master;
|
|
|
- last_src = last_src->mnt_master;
|
|
|
+ while (1) {
|
|
|
+ while (1) {
|
|
|
+ struct mount *next;
|
|
|
+ if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
|
|
|
+ return first_slave(m);
|
|
|
+ next = next_peer(m);
|
|
|
+ if (m->mnt_group_id == origin->mnt_group_id) {
|
|
|
+ if (next == origin)
|
|
|
+ return NULL;
|
|
|
+ } else if (m->mnt_slave.next != &next->mnt_slave)
|
|
|
+ break;
|
|
|
+ m = next;
|
|
|
+ }
|
|
|
+ /* m is the last peer */
|
|
|
+ while (1) {
|
|
|
+ struct mount *master = m->mnt_master;
|
|
|
+ if (m->mnt_slave.next != &master->mnt_slave_list)
|
|
|
+ return next_slave(m);
|
|
|
+ m = next_peer(master);
|
|
|
+ if (master->mnt_group_id == origin->mnt_group_id)
|
|
|
+ break;
|
|
|
+ if (master->mnt_slave.next == &m->mnt_slave)
|
|
|
+ break;
|
|
|
+ m = master;
|
|
|
+ }
|
|
|
+ if (m == origin)
|
|
|
+ return NULL;
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- if (p_last_dest) {
|
|
|
- do {
|
|
|
- p_last_dest = next_peer(p_last_dest);
|
|
|
- } while (IS_MNT_NEW(p_last_dest));
|
|
|
- /* is that a peer of the earlier? */
|
|
|
- if (dest == p_last_dest) {
|
|
|
- *type = CL_MAKE_SHARED;
|
|
|
- return p_last_src;
|
|
|
+/* all accesses are serialized by namespace_sem */
|
|
|
+static struct user_namespace *user_ns;
|
|
|
+static struct mount *last_dest, *last_source, *dest_master;
|
|
|
+static struct mountpoint *mp;
|
|
|
+static struct hlist_head *list;
|
|
|
+
|
|
|
+static int propagate_one(struct mount *m)
|
|
|
+{
|
|
|
+ struct mount *child;
|
|
|
+ int type;
|
|
|
+ /* skip ones added by this propagate_mnt() */
|
|
|
+ if (IS_MNT_NEW(m))
|
|
|
+ return 0;
|
|
|
+ /* skip if mountpoint isn't covered by it */
|
|
|
+ if (!is_subdir(mp->m_dentry, m->mnt.mnt_root))
|
|
|
+ return 0;
|
|
|
+ if (m->mnt_group_id == last_dest->mnt_group_id) {
|
|
|
+ type = CL_MAKE_SHARED;
|
|
|
+ } else {
|
|
|
+ struct mount *n, *p;
|
|
|
+ for (n = m; ; n = p) {
|
|
|
+ p = n->mnt_master;
|
|
|
+ if (p == dest_master || IS_MNT_MARKED(p)) {
|
|
|
+ while (last_dest->mnt_master != p) {
|
|
|
+ last_source = last_source->mnt_master;
|
|
|
+ last_dest = last_source->mnt_parent;
|
|
|
+ }
|
|
|
+ if (n->mnt_group_id != last_dest->mnt_group_id) {
|
|
|
+ last_source = last_source->mnt_master;
|
|
|
+ last_dest = last_source->mnt_parent;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
+ type = CL_SLAVE;
|
|
|
+ /* beginning of peer group among the slaves? */
|
|
|
+ if (IS_MNT_SHARED(m))
|
|
|
+ type |= CL_MAKE_SHARED;
|
|
|
}
|
|
|
- /* slave of the earlier, then */
|
|
|
- *type = CL_SLAVE;
|
|
|
- /* beginning of peer group among the slaves? */
|
|
|
- if (IS_MNT_SHARED(dest))
|
|
|
- *type |= CL_MAKE_SHARED;
|
|
|
- return last_src;
|
|
|
+
|
|
|
+ /* Notice when we are propagating across user namespaces */
|
|
|
+ if (m->mnt_ns->user_ns != user_ns)
|
|
|
+ type |= CL_UNPRIVILEGED;
|
|
|
+ child = copy_tree(last_source, last_source->mnt.mnt_root, type);
|
|
|
+ if (IS_ERR(child))
|
|
|
+ return PTR_ERR(child);
|
|
|
+ mnt_set_mountpoint(m, mp, child);
|
|
|
+ last_dest = m;
|
|
|
+ last_source = child;
|
|
|
+ if (m->mnt_master != dest_master) {
|
|
|
+ read_seqlock_excl(&mount_lock);
|
|
|
+ SET_MNT_MARK(m->mnt_master);
|
|
|
+ read_sequnlock_excl(&mount_lock);
|
|
|
+ }
|
|
|
+ hlist_add_head(&child->mnt_hash, list);
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -222,56 +270,48 @@ static struct mount *get_source(struct mount *dest,
|
|
|
int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
|
|
|
struct mount *source_mnt, struct hlist_head *tree_list)
|
|
|
{
|
|
|
- struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
|
|
|
- struct mount *m, *child;
|
|
|
+ struct mount *m, *n;
|
|
|
int ret = 0;
|
|
|
- struct mount *prev_dest_mnt = dest_mnt;
|
|
|
- struct mount *prev_src_mnt = source_mnt;
|
|
|
- HLIST_HEAD(tmp_list);
|
|
|
-
|
|
|
- for (m = propagation_next(dest_mnt, dest_mnt); m;
|
|
|
- m = propagation_next(m, dest_mnt)) {
|
|
|
- int type;
|
|
|
- struct mount *source;
|
|
|
-
|
|
|
- if (IS_MNT_NEW(m))
|
|
|
- continue;
|
|
|
-
|
|
|
- source = get_source(m, prev_dest_mnt, prev_src_mnt, &type);
|
|
|
-
|
|
|
- /* Notice when we are propagating across user namespaces */
|
|
|
- if (m->mnt_ns->user_ns != user_ns)
|
|
|
- type |= CL_UNPRIVILEGED;
|
|
|
-
|
|
|
- child = copy_tree(source, source->mnt.mnt_root, type);
|
|
|
- if (IS_ERR(child)) {
|
|
|
- ret = PTR_ERR(child);
|
|
|
- tmp_list = *tree_list;
|
|
|
- tmp_list.first->pprev = &tmp_list.first;
|
|
|
- INIT_HLIST_HEAD(tree_list);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * we don't want to bother passing tons of arguments to
|
|
|
+ * propagate_one(); everything is serialized by namespace_sem,
|
|
|
+ * so globals will do just fine.
|
|
|
+ */
|
|
|
+ user_ns = current->nsproxy->mnt_ns->user_ns;
|
|
|
+ last_dest = dest_mnt;
|
|
|
+ last_source = source_mnt;
|
|
|
+ mp = dest_mp;
|
|
|
+ list = tree_list;
|
|
|
+ dest_master = dest_mnt->mnt_master;
|
|
|
+
|
|
|
+ /* all peers of dest_mnt, except dest_mnt itself */
|
|
|
+ for (n = next_peer(dest_mnt); n != dest_mnt; n = next_peer(n)) {
|
|
|
+ ret = propagate_one(n);
|
|
|
+ if (ret)
|
|
|
goto out;
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if (is_subdir(dest_mp->m_dentry, m->mnt.mnt_root)) {
|
|
|
- mnt_set_mountpoint(m, dest_mp, child);
|
|
|
- hlist_add_head(&child->mnt_hash, tree_list);
|
|
|
- } else {
|
|
|
- /*
|
|
|
- * This can happen if the parent mount was bind mounted
|
|
|
- * on some subdirectory of a shared/slave mount.
|
|
|
- */
|
|
|
- hlist_add_head(&child->mnt_hash, &tmp_list);
|
|
|
- }
|
|
|
- prev_dest_mnt = m;
|
|
|
- prev_src_mnt = child;
|
|
|
+ /* all slave groups */
|
|
|
+ for (m = next_group(dest_mnt, dest_mnt); m;
|
|
|
+ m = next_group(m, dest_mnt)) {
|
|
|
+ /* everything in that slave group */
|
|
|
+ n = m;
|
|
|
+ do {
|
|
|
+ ret = propagate_one(n);
|
|
|
+ if (ret)
|
|
|
+ goto out;
|
|
|
+ n = next_peer(n);
|
|
|
+ } while (n != m);
|
|
|
}
|
|
|
out:
|
|
|
- lock_mount_hash();
|
|
|
- while (!hlist_empty(&tmp_list)) {
|
|
|
- child = hlist_entry(tmp_list.first, struct mount, mnt_hash);
|
|
|
- umount_tree(child, 0);
|
|
|
+ read_seqlock_excl(&mount_lock);
|
|
|
+ hlist_for_each_entry(n, tree_list, mnt_hash) {
|
|
|
+ m = n->mnt_parent;
|
|
|
+ if (m->mnt_master != dest_mnt->mnt_master)
|
|
|
+ CLEAR_MNT_MARK(m->mnt_master);
|
|
|
}
|
|
|
- unlock_mount_hash();
|
|
|
+ read_sequnlock_excl(&mount_lock);
|
|
|
return ret;
|
|
|
}
|
|
|
|