[PATCH] shared mount handling: bind and rbind
Implement handling of MS_BIND in presense of shared mounts (see Documentation/sharedsubtree.txt in the end of patch series for detailed description). Signed-off-by: Ram Pai <linuxram@us.ibm.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
03e06e68ff
commit
b90fa9ae8f
126
fs/namespace.c
126
fs/namespace.c
|
@ -28,8 +28,6 @@
|
||||||
|
|
||||||
extern int __init init_rootfs(void);
|
extern int __init init_rootfs(void);
|
||||||
|
|
||||||
#define CL_EXPIRE 0x01
|
|
||||||
|
|
||||||
#ifdef CONFIG_SYSFS
|
#ifdef CONFIG_SYSFS
|
||||||
extern int __init sysfs_init(void);
|
extern int __init sysfs_init(void);
|
||||||
#else
|
#else
|
||||||
|
@ -145,13 +143,43 @@ static void detach_mnt(struct vfsmount *mnt, struct nameidata *old_nd)
|
||||||
old_nd->dentry->d_mounted--;
|
old_nd->dentry->d_mounted--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry,
|
||||||
|
struct vfsmount *child_mnt)
|
||||||
|
{
|
||||||
|
child_mnt->mnt_parent = mntget(mnt);
|
||||||
|
child_mnt->mnt_mountpoint = dget(dentry);
|
||||||
|
dentry->d_mounted++;
|
||||||
|
}
|
||||||
|
|
||||||
static void attach_mnt(struct vfsmount *mnt, struct nameidata *nd)
|
static void attach_mnt(struct vfsmount *mnt, struct nameidata *nd)
|
||||||
{
|
{
|
||||||
mnt->mnt_parent = mntget(nd->mnt);
|
mnt_set_mountpoint(nd->mnt, nd->dentry, mnt);
|
||||||
mnt->mnt_mountpoint = dget(nd->dentry);
|
list_add_tail(&mnt->mnt_hash, mount_hashtable +
|
||||||
list_add(&mnt->mnt_hash, mount_hashtable + hash(nd->mnt, nd->dentry));
|
hash(nd->mnt, nd->dentry));
|
||||||
list_add_tail(&mnt->mnt_child, &nd->mnt->mnt_mounts);
|
list_add_tail(&mnt->mnt_child, &nd->mnt->mnt_mounts);
|
||||||
nd->dentry->d_mounted++;
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the caller must hold vfsmount_lock
|
||||||
|
*/
|
||||||
|
static void commit_tree(struct vfsmount *mnt)
|
||||||
|
{
|
||||||
|
struct vfsmount *parent = mnt->mnt_parent;
|
||||||
|
struct vfsmount *m;
|
||||||
|
LIST_HEAD(head);
|
||||||
|
struct namespace *n = parent->mnt_namespace;
|
||||||
|
|
||||||
|
BUG_ON(parent == mnt);
|
||||||
|
|
||||||
|
list_add_tail(&head, &mnt->mnt_list);
|
||||||
|
list_for_each_entry(m, &head, mnt_list)
|
||||||
|
m->mnt_namespace = n;
|
||||||
|
list_splice(&head, n->list.prev);
|
||||||
|
|
||||||
|
list_add_tail(&mnt->mnt_hash, mount_hashtable +
|
||||||
|
hash(parent, mnt->mnt_mountpoint));
|
||||||
|
list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
|
||||||
|
touch_namespace(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct vfsmount *next_mnt(struct vfsmount *p, struct vfsmount *root)
|
static struct vfsmount *next_mnt(struct vfsmount *p, struct vfsmount *root)
|
||||||
|
@ -183,7 +211,11 @@ static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
|
||||||
mnt->mnt_root = dget(root);
|
mnt->mnt_root = dget(root);
|
||||||
mnt->mnt_mountpoint = mnt->mnt_root;
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
||||||
mnt->mnt_parent = mnt;
|
mnt->mnt_parent = mnt;
|
||||||
mnt->mnt_namespace = current->namespace;
|
|
||||||
|
if ((flag & CL_PROPAGATION) || IS_MNT_SHARED(old))
|
||||||
|
list_add(&mnt->mnt_share, &old->mnt_share);
|
||||||
|
if (flag & CL_MAKE_SHARED)
|
||||||
|
set_mnt_shared(mnt);
|
||||||
|
|
||||||
/* stick the duplicate mount on the same expiry list
|
/* stick the duplicate mount on the same expiry list
|
||||||
* as the original if that was on one */
|
* as the original if that was on one */
|
||||||
|
@ -379,7 +411,7 @@ int may_umount(struct vfsmount *mnt)
|
||||||
|
|
||||||
EXPORT_SYMBOL(may_umount);
|
EXPORT_SYMBOL(may_umount);
|
||||||
|
|
||||||
static void release_mounts(struct list_head *head)
|
void release_mounts(struct list_head *head)
|
||||||
{
|
{
|
||||||
struct vfsmount *mnt;
|
struct vfsmount *mnt;
|
||||||
while(!list_empty(head)) {
|
while(!list_empty(head)) {
|
||||||
|
@ -401,7 +433,7 @@ static void release_mounts(struct list_head *head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void umount_tree(struct vfsmount *mnt, struct list_head *kill)
|
void umount_tree(struct vfsmount *mnt, struct list_head *kill)
|
||||||
{
|
{
|
||||||
struct vfsmount *p;
|
struct vfsmount *p;
|
||||||
|
|
||||||
|
@ -581,7 +613,7 @@ static int lives_below_in_same_fs(struct dentry *d, struct dentry *dentry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry,
|
struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry,
|
||||||
int flag)
|
int flag)
|
||||||
{
|
{
|
||||||
struct vfsmount *res, *p, *q, *r, *s;
|
struct vfsmount *res, *p, *q, *r, *s;
|
||||||
|
@ -626,6 +658,67 @@ Enomem:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @source_mnt : mount tree to be attached
|
||||||
|
* @nd : place the mount tree @source_mnt is attached
|
||||||
|
*
|
||||||
|
* NOTE: in the table below explains the semantics when a source mount
|
||||||
|
* of a given type is attached to a destination mount of a given type.
|
||||||
|
* ---------------------------------------------
|
||||||
|
* | BIND MOUNT OPERATION |
|
||||||
|
* |********************************************
|
||||||
|
* | source-->| shared | private |
|
||||||
|
* | dest | | |
|
||||||
|
* | | | | |
|
||||||
|
* | v | | |
|
||||||
|
* |********************************************
|
||||||
|
* | shared | shared (++) | shared (+) |
|
||||||
|
* | | | |
|
||||||
|
* |non-shared| shared (+) | private |
|
||||||
|
* *********************************************
|
||||||
|
* A bind operation clones the source mount and mounts the clone on the
|
||||||
|
* destination mount.
|
||||||
|
*
|
||||||
|
* (++) the cloned mount is propagated to all the mounts in the propagation
|
||||||
|
* tree of the destination mount and the cloned mount is added to
|
||||||
|
* the peer group of the source mount.
|
||||||
|
* (+) the cloned mount is created under the destination mount and is marked
|
||||||
|
* as shared. The cloned mount is added to the peer group of the source
|
||||||
|
* mount.
|
||||||
|
*
|
||||||
|
* if the source mount is a tree, the operations explained above is
|
||||||
|
* applied to each mount in the tree.
|
||||||
|
* Must be called without spinlocks held, since this function can sleep
|
||||||
|
* in allocations.
|
||||||
|
*/
|
||||||
|
static int attach_recursive_mnt(struct vfsmount *source_mnt,
|
||||||
|
struct nameidata *nd)
|
||||||
|
{
|
||||||
|
LIST_HEAD(tree_list);
|
||||||
|
struct vfsmount *dest_mnt = nd->mnt;
|
||||||
|
struct dentry *dest_dentry = nd->dentry;
|
||||||
|
struct vfsmount *child, *p;
|
||||||
|
|
||||||
|
if (propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (IS_MNT_SHARED(dest_mnt)) {
|
||||||
|
for (p = source_mnt; p; p = next_mnt(p, source_mnt))
|
||||||
|
set_mnt_shared(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock(&vfsmount_lock);
|
||||||
|
mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
|
||||||
|
commit_tree(source_mnt);
|
||||||
|
|
||||||
|
list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
|
||||||
|
list_del_init(&child->mnt_hash);
|
||||||
|
commit_tree(child);
|
||||||
|
}
|
||||||
|
spin_unlock(&vfsmount_lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int graft_tree(struct vfsmount *mnt, struct nameidata *nd)
|
static int graft_tree(struct vfsmount *mnt, struct nameidata *nd)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
@ -646,17 +739,8 @@ static int graft_tree(struct vfsmount *mnt, struct nameidata *nd)
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
|
|
||||||
err = -ENOENT;
|
err = -ENOENT;
|
||||||
spin_lock(&vfsmount_lock);
|
if (IS_ROOT(nd->dentry) || !d_unhashed(nd->dentry))
|
||||||
if (IS_ROOT(nd->dentry) || !d_unhashed(nd->dentry)) {
|
err = attach_recursive_mnt(mnt, nd);
|
||||||
struct list_head head;
|
|
||||||
|
|
||||||
attach_mnt(mnt, nd);
|
|
||||||
list_add_tail(&head, &mnt->mnt_list);
|
|
||||||
list_splice(&head, current->namespace->list.prev);
|
|
||||||
err = 0;
|
|
||||||
touch_namespace(current->namespace);
|
|
||||||
}
|
|
||||||
spin_unlock(&vfsmount_lock);
|
|
||||||
out_unlock:
|
out_unlock:
|
||||||
up(&nd->dentry->d_inode->i_sem);
|
up(&nd->dentry->d_inode->i_sem);
|
||||||
if (!err)
|
if (!err)
|
||||||
|
|
81
fs/pnode.c
81
fs/pnode.c
|
@ -20,9 +20,88 @@ static inline struct vfsmount *next_peer(struct vfsmount *p)
|
||||||
void change_mnt_propagation(struct vfsmount *mnt, int type)
|
void change_mnt_propagation(struct vfsmount *mnt, int type)
|
||||||
{
|
{
|
||||||
if (type == MS_SHARED) {
|
if (type == MS_SHARED) {
|
||||||
mnt->mnt_flags |= MNT_SHARED;
|
set_mnt_shared(mnt);
|
||||||
} else {
|
} else {
|
||||||
list_del_init(&mnt->mnt_share);
|
list_del_init(&mnt->mnt_share);
|
||||||
mnt->mnt_flags &= ~MNT_PNODE_MASK;
|
mnt->mnt_flags &= ~MNT_PNODE_MASK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get the next mount in the propagation tree.
|
||||||
|
* @m: the mount seen last
|
||||||
|
* @origin: the original mount from where the tree walk initiated
|
||||||
|
*/
|
||||||
|
static struct vfsmount *propagation_next(struct vfsmount *m,
|
||||||
|
struct vfsmount *origin)
|
||||||
|
{
|
||||||
|
m = next_peer(m);
|
||||||
|
if (m == origin)
|
||||||
|
return NULL;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mount 'source_mnt' under the destination 'dest_mnt' at
|
||||||
|
* dentry 'dest_dentry'. And propagate that mount to
|
||||||
|
* all the peer and slave mounts of 'dest_mnt'.
|
||||||
|
* Link all the new mounts into a propagation tree headed at
|
||||||
|
* source_mnt. Also link all the new mounts using ->mnt_list
|
||||||
|
* headed at source_mnt's ->mnt_list
|
||||||
|
*
|
||||||
|
* @dest_mnt: destination mount.
|
||||||
|
* @dest_dentry: destination dentry.
|
||||||
|
* @source_mnt: source mount.
|
||||||
|
* @tree_list : list of heads of trees to be attached.
|
||||||
|
*/
|
||||||
|
int propagate_mnt(struct vfsmount *dest_mnt, struct dentry *dest_dentry,
|
||||||
|
struct vfsmount *source_mnt, struct list_head *tree_list)
|
||||||
|
{
|
||||||
|
struct vfsmount *m, *child;
|
||||||
|
int ret = 0;
|
||||||
|
struct vfsmount *prev_dest_mnt = dest_mnt;
|
||||||
|
struct vfsmount *prev_src_mnt = source_mnt;
|
||||||
|
LIST_HEAD(tmp_list);
|
||||||
|
LIST_HEAD(umount_list);
|
||||||
|
|
||||||
|
for (m = propagation_next(dest_mnt, dest_mnt); m;
|
||||||
|
m = propagation_next(m, dest_mnt)) {
|
||||||
|
int type = CL_PROPAGATION;
|
||||||
|
|
||||||
|
if (IS_MNT_NEW(m))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (IS_MNT_SHARED(m))
|
||||||
|
type |= CL_MAKE_SHARED;
|
||||||
|
|
||||||
|
if (!(child = copy_tree(source_mnt, source_mnt->mnt_root,
|
||||||
|
type))) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
list_splice(tree_list, tmp_list.prev);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_subdir(dest_dentry, m->mnt_root)) {
|
||||||
|
mnt_set_mountpoint(m, dest_dentry, child);
|
||||||
|
list_add_tail(&child->mnt_hash, tree_list);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* This can happen if the parent mount was bind mounted
|
||||||
|
* on some subdirectory of a shared/slave mount.
|
||||||
|
*/
|
||||||
|
list_add_tail(&child->mnt_hash, &tmp_list);
|
||||||
|
}
|
||||||
|
prev_dest_mnt = m;
|
||||||
|
prev_src_mnt = child;
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
spin_lock(&vfsmount_lock);
|
||||||
|
while (!list_empty(&tmp_list)) {
|
||||||
|
child = list_entry(tmp_list.next, struct vfsmount, mnt_hash);
|
||||||
|
list_del_init(&child->mnt_hash);
|
||||||
|
umount_tree(child, &umount_list);
|
||||||
|
}
|
||||||
|
spin_unlock(&vfsmount_lock);
|
||||||
|
release_mounts(&umount_list);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
14
fs/pnode.h
14
fs/pnode.h
|
@ -12,7 +12,21 @@
|
||||||
#include <linux/mount.h>
|
#include <linux/mount.h>
|
||||||
|
|
||||||
#define IS_MNT_SHARED(mnt) (mnt->mnt_flags & MNT_SHARED)
|
#define IS_MNT_SHARED(mnt) (mnt->mnt_flags & MNT_SHARED)
|
||||||
|
#define IS_MNT_NEW(mnt) (!mnt->mnt_namespace)
|
||||||
#define CLEAR_MNT_SHARED(mnt) (mnt->mnt_flags &= ~MNT_SHARED)
|
#define CLEAR_MNT_SHARED(mnt) (mnt->mnt_flags &= ~MNT_SHARED)
|
||||||
|
|
||||||
|
#define CL_EXPIRE 0x01
|
||||||
|
#define CL_COPY_ALL 0x04
|
||||||
|
#define CL_MAKE_SHARED 0x08
|
||||||
|
#define CL_PROPAGATION 0x10
|
||||||
|
|
||||||
|
static inline void set_mnt_shared(struct vfsmount *mnt)
|
||||||
|
{
|
||||||
|
mnt->mnt_flags &= ~MNT_PNODE_MASK;
|
||||||
|
mnt->mnt_flags |= MNT_SHARED;
|
||||||
|
}
|
||||||
|
|
||||||
void change_mnt_propagation(struct vfsmount *, int);
|
void change_mnt_propagation(struct vfsmount *, int);
|
||||||
|
int propagate_mnt(struct vfsmount *, struct dentry *, struct vfsmount *,
|
||||||
|
struct list_head *);
|
||||||
#endif /* _LINUX_PNODE_H */
|
#endif /* _LINUX_PNODE_H */
|
||||||
|
|
|
@ -1251,7 +1251,12 @@ extern int unregister_filesystem(struct file_system_type *);
|
||||||
extern struct vfsmount *kern_mount(struct file_system_type *);
|
extern struct vfsmount *kern_mount(struct file_system_type *);
|
||||||
extern int may_umount_tree(struct vfsmount *);
|
extern int may_umount_tree(struct vfsmount *);
|
||||||
extern int may_umount(struct vfsmount *);
|
extern int may_umount(struct vfsmount *);
|
||||||
|
extern void umount_tree(struct vfsmount *, struct list_head *);
|
||||||
|
extern void release_mounts(struct list_head *);
|
||||||
extern long do_mount(char *, char *, char *, unsigned long, void *);
|
extern long do_mount(char *, char *, char *, unsigned long, void *);
|
||||||
|
extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);
|
||||||
|
extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *,
|
||||||
|
struct vfsmount *);
|
||||||
|
|
||||||
extern int vfs_statfs(struct super_block *, struct kstatfs *);
|
extern int vfs_statfs(struct super_block *, struct kstatfs *);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue