From: Valerie Aurora <vaurora@xxxxxxxxxx> A union mount clones the vfsmount tree of all of the read-only layers of the union and keeps a reference to it in the vfsmount of the topmost layer of the union. clone_union_tree() takes the path of the proposed union mountpoint and attempts to clones every vfsmount mounted at that same pathname, as well as their submounts. All these mounts must be read-only, not slave, and not shared. put_union_sb() unwinds everything clone_union_tree() does. It is called when the superblock is deactivated. Thus, you can lazy unmount a union mount and when the last reference goes away, the union will be torn down. Original-author: Valerie Aurora <vaurora@xxxxxxxxxx> Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/namespace.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mount.h | 2 + 2 files changed, 69 insertions(+), 0 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index bed9ccd..5fbe3b0 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1450,6 +1450,73 @@ static int check_topmost_union_mnt(struct vfsmount *topmost_mnt, int mnt_flags) #endif } +void put_union_sb(struct super_block *sb) +{ + if (unlikely(sb->s_union_lower_mnts)) { + struct mount *mnt = real_mount(sb->s_union_lower_mnts); + LIST_HEAD(umount_list); + + br_write_lock(vfsmount_lock); + umount_tree(mnt, 0, &umount_list); + br_write_unlock(vfsmount_lock); + release_mounts(&umount_list); + sb->s_union_lower_mnts = NULL; + sb->s_union_count = 0; + } +} + +/** + * clone_union_tree - Clone all union-able mounts at this mountpoint + * @topmost: vfsmount of topmost layer + * @mntpnt: target of union mount + * + * Given the target mountpoint of a union mount, clone all the mounts at that + * mountpoint (well, pathname) that qualify as a union lower layer. Increment + * the hard readonly count of the lower layer superblocks. + * + * Returns error if any of the mounts or submounts mounted on or below this + * pathname are unsuitable for union mounting. This means you can't construct + * a union mount at the root of an existing mount without unioning it. + * + * XXX - Maybe should take # of layers to go down as an argument. But how to + * pass this in through mount options? All solutions look ugly. Currently you + * express your intention through mounting file systems on the same mountpoint, + * which is pretty elegant. + */ +static int clone_union_tree(struct mount *topmost, struct path *mntpnt) +{ + struct mount *mnt, *cloned_tree; + + if (!IS_ROOT(mntpnt->dentry)) { + printk(KERN_INFO "union mount: mount point must be a root dir\n"); + return -EINVAL; + } + + /* Look for the "lowest" layer to union. */ + mnt = real_mount(mntpnt->mnt); + while (mnt->mnt_parent->mnt.mnt_root == mnt->mnt_mountpoint) { + /* Got root (mnt)? */ + if (mnt->mnt_parent == mnt) + break; + mnt = mnt->mnt_parent; + } + + /* Clone all the read-only mounts and submounts, only if they + * are not shared or slave, and increment the hard read-only + * users count on each one. If this can't be done for every + * mount and submount below this one, fail. + */ + cloned_tree = copy_tree(mnt, mnt->mnt.mnt_root, + CL_COPY_ALL | CL_PRIVATE | + CL_NO_SHARED | CL_NO_SLAVE | + CL_MAKE_HARD_READONLY); + if (IS_ERR(cloned_tree)) + return PTR_ERR(cloned_tree); + + topmost->mnt.mnt_sb->s_union_lower_mnts = &cloned_tree->mnt; + return 0; +} + /* * @source_mnt : mount tree to be attached * @nd : place the mount tree @source_mnt is attached diff --git a/include/linux/mount.h b/include/linux/mount.h index 0ba1def..67f46fa 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -78,4 +78,6 @@ extern void mark_mounts_for_expiry(struct list_head *mounts); extern dev_t name_to_dev_t(char *name); +extern void put_union_sb(struct super_block *sb); + #endif /* _LINUX_MOUNT_H */ -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html