--- a/fs/namespace.c +++ b/fs/namespace.c @@ -1656,8 +1656,10 @@ static int do_move_mount(struct path *pa /* moving to or from a union mount is not supported */ err = -EINVAL; +#if 0 if (IS_MNT_UNION(path->mnt)) goto exit; +#endif if (IS_MNT_UNION(old_path.mnt)) goto exit; --- a/fs/union.c +++ b/fs/union.c @@ -260,8 +260,6 @@ int append_to_union(struct vfsmount *mnt spin_lock(&union_lock); um = union_lookup(dentry, mnt); if (um) { - BUG_ON((um->u_next.dentry != dest_dentry) || - (um->u_next.mnt != dest_mnt)); spin_unlock(&union_lock); union_put(this); return 0; @@ -274,6 +272,23 @@ int append_to_union(struct vfsmount *mnt return 0; } +int follow_union_mountpoint(struct path *path) +{ + struct path new_path = *path; + + path_get(&new_path); + while (follow_union_down(&new_path)) { + if (new_path.dentry != new_path.mnt->mnt_root) + continue; + + path_put(path); + *path = new_path; + return 1; + } + path_put(&new_path); + return 0; +} + /* * follow_union_down - follow the union stack one layer down * --- a/include/linux/union.h +++ b/include/linux/union.h @@ -47,6 +47,7 @@ extern int append_to_union(struct vfsmou struct vfsmount *, struct dentry *); extern int follow_union_down(struct path *); extern int follow_union_mount(struct path *); +extern int follow_union_mountpoint(struct path *path); extern void __d_drop_unions(struct dentry *); extern void shrink_d_unions(struct dentry *); extern void __shrink_d_unions(struct dentry *, struct list_head *); @@ -68,6 +69,7 @@ extern int union_permission(struct path #define append_to_union(x1, y1, x2, y2) ({ BUG(); (0); }) #define follow_union_down(x) ({ (0); }) #define follow_union_mount(x) ({ (0); }) +#define follow_union_mountpoint(x) ({ (0); }) #define __d_drop_unions(x) do { } while (0) #define shrink_d_unions(x) do { } while (0) #define __shrink_d_unions(x,y) do { } while (0) --- a/fs/namei.c +++ b/fs/namei.c @@ -626,6 +626,9 @@ static int cache_lookup_union(struct nam !S_ISDIR(path->dentry->d_inode->i_mode)) goto out; + if (follow_union_mountpoint(path)) + goto out; + /* Build the union stack for this part */ res = __cache_lookup_build_union(nd, name, path); if (res) { @@ -892,6 +895,9 @@ static int real_lookup_union(struct name !S_ISDIR(path->dentry->d_inode->i_mode)) goto out; + if (follow_union_mountpoint(path)) + goto out; + /* Build the union stack for this part */ res = __real_lookup_build_union(nd, name, path); if (res) { @@ -1813,6 +1819,9 @@ int hash_lookup_union(struct nameidata * !S_ISDIR(path->dentry->d_inode->i_mode)) goto out; + if (follow_union_mountpoint(path)) + goto out; + /* Build the union stack for this part */ res = __hash_lookup_build_union(nd, name, path); if (res) { --- a/fs/readdir.c +++ b/fs/readdir.c @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -45,7 +46,7 @@ int vfs_readdir(struct file *file, filld * below this one in the union stack. */ if (is_unionized(file->f_path.dentry, file->f_path.mnt) && - !IS_OPAQUE(inode)) { + !IS_OPAQUE(inode) && IS_MNT_UNION(file->f_path.mnt)) { res = union_copyup_dir(&file->f_path); if (res) goto out_unlock;