Skip to content

Commit

Permalink
[patch 4/7] vfs: mountinfo: add mount peer group ID
Browse files Browse the repository at this point in the history
Add a unique ID to each peer group using the IDR infrastructure.  The
identifiers are reused after the peer group dissolves.

The IDR structures are protected by holding namepspace_sem for write
while allocating or deallocating IDs.

IDs are allocated when a previously unshared vfsmount becomes the
first member of a peer group.  When a new member is added to an
existing group, the ID is copied from one of the old members.

IDs are freed when the last member of a peer group is unshared.

Setting the MNT_SHARED flag on members of a subtree is done as a
separate step, after all the IDs have been allocated.  This way an
allocation failure can be cleaned up easilty, without affecting the
propagation state.

Based on design sketch by Al Viro.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
  • Loading branch information
Miklos Szeredi authored and Al Viro committed Apr 23, 2008
1 parent 73cd49e commit 719f5d7
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 4 deletions.
93 changes: 90 additions & 3 deletions fs/namespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ __cacheline_aligned_in_smp DEFINE_SPINLOCK(vfsmount_lock);

static int event;
static DEFINE_IDA(mnt_id_ida);
static DEFINE_IDA(mnt_group_ida);

static struct list_head *mount_hashtable __read_mostly;
static struct kmem_cache *mnt_cache __read_mostly;
Expand Down Expand Up @@ -83,6 +84,28 @@ static void mnt_free_id(struct vfsmount *mnt)
spin_unlock(&vfsmount_lock);
}

/*
* Allocate a new peer group ID
*
* mnt_group_ida is protected by namespace_sem
*/
static int mnt_alloc_group_id(struct vfsmount *mnt)
{
if (!ida_pre_get(&mnt_group_ida, GFP_KERNEL))
return -ENOMEM;

return ida_get_new_above(&mnt_group_ida, 1, &mnt->mnt_group_id);
}

/*
* Release a peer group ID
*/
void mnt_release_group_id(struct vfsmount *mnt)
{
ida_remove(&mnt_group_ida, mnt->mnt_group_id);
mnt->mnt_group_id = 0;
}

struct vfsmount *alloc_vfsmnt(const char *name)
{
struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
Expand Down Expand Up @@ -533,6 +556,17 @@ static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
struct vfsmount *mnt = alloc_vfsmnt(old->mnt_devname);

if (mnt) {
if (flag & (CL_SLAVE | CL_PRIVATE))
mnt->mnt_group_id = 0; /* not a peer of original */
else
mnt->mnt_group_id = old->mnt_group_id;

if ((flag & CL_MAKE_SHARED) && !mnt->mnt_group_id) {
int err = mnt_alloc_group_id(mnt);
if (err)
goto out_free;
}

mnt->mnt_flags = old->mnt_flags;
atomic_inc(&sb->s_active);
mnt->mnt_sb = sb;
Expand Down Expand Up @@ -562,6 +596,10 @@ static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
}
}
return mnt;

out_free:
free_vfsmnt(mnt);
return NULL;
}

static inline void __mntput(struct vfsmount *mnt)
Expand Down Expand Up @@ -1142,6 +1180,33 @@ void drop_collected_mounts(struct vfsmount *mnt)
release_mounts(&umount_list);
}

static void cleanup_group_ids(struct vfsmount *mnt, struct vfsmount *end)
{
struct vfsmount *p;

for (p = mnt; p != end; p = next_mnt(p, mnt)) {
if (p->mnt_group_id && !IS_MNT_SHARED(p))
mnt_release_group_id(p);
}
}

static int invent_group_ids(struct vfsmount *mnt, bool recurse)
{
struct vfsmount *p;

for (p = mnt; p; p = recurse ? next_mnt(p, mnt) : NULL) {
if (!p->mnt_group_id && !IS_MNT_SHARED(p)) {
int err = mnt_alloc_group_id(p);
if (err) {
cleanup_group_ids(mnt, p);
return err;
}
}
}

return 0;
}

/*
* @source_mnt : mount tree to be attached
* @nd : place the mount tree @source_mnt is attached
Expand Down Expand Up @@ -1212,9 +1277,16 @@ static int attach_recursive_mnt(struct vfsmount *source_mnt,
struct vfsmount *dest_mnt = path->mnt;
struct dentry *dest_dentry = path->dentry;
struct vfsmount *child, *p;
int err;

if (propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list))
return -EINVAL;
if (IS_MNT_SHARED(dest_mnt)) {
err = invent_group_ids(source_mnt, true);
if (err)
goto out;
}
err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list);
if (err)
goto out_cleanup_ids;

if (IS_MNT_SHARED(dest_mnt)) {
for (p = source_mnt; p; p = next_mnt(p, source_mnt))
Expand All @@ -1237,6 +1309,12 @@ static int attach_recursive_mnt(struct vfsmount *source_mnt,
}
spin_unlock(&vfsmount_lock);
return 0;

out_cleanup_ids:
if (IS_MNT_SHARED(dest_mnt))
cleanup_group_ids(source_mnt, NULL);
out:
return err;
}

static int graft_tree(struct vfsmount *mnt, struct path *path)
Expand Down Expand Up @@ -1277,6 +1355,7 @@ static noinline int do_change_type(struct nameidata *nd, int flag)
struct vfsmount *m, *mnt = nd->path.mnt;
int recurse = flag & MS_REC;
int type = flag & ~MS_REC;
int err = 0;

if (!capable(CAP_SYS_ADMIN))
return -EPERM;
Expand All @@ -1285,12 +1364,20 @@ static noinline int do_change_type(struct nameidata *nd, int flag)
return -EINVAL;

down_write(&namespace_sem);
if (type == MS_SHARED) {
err = invent_group_ids(mnt, recurse);
if (err)
goto out_unlock;
}

spin_lock(&vfsmount_lock);
for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL))
change_mnt_propagation(m, type);
spin_unlock(&vfsmount_lock);

out_unlock:
up_write(&namespace_sem);
return 0;
return err;
}

/*
Expand Down
5 changes: 4 additions & 1 deletion fs/pnode.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ static int do_make_slave(struct vfsmount *mnt)
if (peer_mnt == mnt)
peer_mnt = NULL;
}
if (IS_MNT_SHARED(mnt) && list_empty(&mnt->mnt_share))
mnt_release_group_id(mnt);

list_del_init(&mnt->mnt_share);
mnt->mnt_group_id = 0;

if (peer_mnt)
master = peer_mnt;
Expand All @@ -68,7 +72,6 @@ static int do_make_slave(struct vfsmount *mnt)
}
mnt->mnt_master = master;
CLEAR_MNT_SHARED(mnt);
INIT_LIST_HEAD(&mnt->mnt_slave_list);
return 0;
}

Expand Down
1 change: 1 addition & 0 deletions include/linux/mount.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct vfsmount {
struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace */
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
/*
* We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
* to let these frequently modified fields in a separate cache line
Expand Down

0 comments on commit 719f5d7

Please sign in to comment.