Commit a05964f3 authored by Ram Pai's avatar Ram Pai Committed by Linus Torvalds

[PATCH] shared mounts handling: umount

An unmount of a mount creates a umount event on the parent.  If the
parent is a shared mount, it gets propagated to all mounts in the peer
group.
Signed-off-by: default avatarRam Pai <linuxram@us.ibm.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 21444403
...@@ -86,31 +86,44 @@ void free_vfsmnt(struct vfsmount *mnt) ...@@ -86,31 +86,44 @@ void free_vfsmnt(struct vfsmount *mnt)
} }
/* /*
* Now, lookup_mnt increments the ref count before returning * find the first or last mount at @dentry on vfsmount @mnt depending on
* the vfsmount struct. * @dir. If @dir is set return the first mount else return the last mount.
*/ */
struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry) struct vfsmount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry,
int dir)
{ {
struct list_head *head = mount_hashtable + hash(mnt, dentry); struct list_head *head = mount_hashtable + hash(mnt, dentry);
struct list_head *tmp = head; struct list_head *tmp = head;
struct vfsmount *p, *found = NULL; struct vfsmount *p, *found = NULL;
spin_lock(&vfsmount_lock);
for (;;) { for (;;) {
tmp = tmp->next; tmp = dir ? tmp->next : tmp->prev;
p = NULL; p = NULL;
if (tmp == head) if (tmp == head)
break; break;
p = list_entry(tmp, struct vfsmount, mnt_hash); p = list_entry(tmp, struct vfsmount, mnt_hash);
if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) { if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
found = mntget(p); found = p;
break; break;
} }
} }
spin_unlock(&vfsmount_lock);
return found; return found;
} }
/*
* lookup_mnt increments the ref count before returning
* the vfsmount struct.
*/
struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
{
struct vfsmount *child_mnt;
spin_lock(&vfsmount_lock);
if ((child_mnt = __lookup_mnt(mnt, dentry, 1)))
mntget(child_mnt);
spin_unlock(&vfsmount_lock);
return child_mnt;
}
static inline int check_mnt(struct vfsmount *mnt) static inline int check_mnt(struct vfsmount *mnt)
{ {
return mnt->mnt_namespace == current->namespace; return mnt->mnt_namespace == current->namespace;
...@@ -404,9 +417,12 @@ EXPORT_SYMBOL(may_umount_tree); ...@@ -404,9 +417,12 @@ EXPORT_SYMBOL(may_umount_tree);
*/ */
int may_umount(struct vfsmount *mnt) int may_umount(struct vfsmount *mnt)
{ {
if (atomic_read(&mnt->mnt_count) > 2) int ret = 0;
return -EBUSY; spin_lock(&vfsmount_lock);
return 0; if (propagate_mount_busy(mnt, 2))
ret = -EBUSY;
spin_unlock(&vfsmount_lock);
return ret;
} }
EXPORT_SYMBOL(may_umount); EXPORT_SYMBOL(may_umount);
...@@ -433,7 +449,7 @@ void release_mounts(struct list_head *head) ...@@ -433,7 +449,7 @@ void release_mounts(struct list_head *head)
} }
} }
void umount_tree(struct vfsmount *mnt, struct list_head *kill) void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill)
{ {
struct vfsmount *p; struct vfsmount *p;
...@@ -442,6 +458,9 @@ void umount_tree(struct vfsmount *mnt, struct list_head *kill) ...@@ -442,6 +458,9 @@ void umount_tree(struct vfsmount *mnt, struct list_head *kill)
list_add(&p->mnt_hash, kill); list_add(&p->mnt_hash, kill);
} }
if (propagate)
propagate_umount(kill);
list_for_each_entry(p, kill, mnt_hash) { list_for_each_entry(p, kill, mnt_hash) {
list_del_init(&p->mnt_expire); list_del_init(&p->mnt_expire);
list_del_init(&p->mnt_list); list_del_init(&p->mnt_list);
...@@ -450,6 +469,7 @@ void umount_tree(struct vfsmount *mnt, struct list_head *kill) ...@@ -450,6 +469,7 @@ void umount_tree(struct vfsmount *mnt, struct list_head *kill)
list_del_init(&p->mnt_child); list_del_init(&p->mnt_child);
if (p->mnt_parent != p) if (p->mnt_parent != p)
mnt->mnt_mountpoint->d_mounted--; mnt->mnt_mountpoint->d_mounted--;
change_mnt_propagation(p, MS_PRIVATE);
} }
} }
...@@ -526,9 +546,9 @@ static int do_umount(struct vfsmount *mnt, int flags) ...@@ -526,9 +546,9 @@ static int do_umount(struct vfsmount *mnt, int flags)
event++; event++;
retval = -EBUSY; retval = -EBUSY;
if (atomic_read(&mnt->mnt_count) == 2 || flags & MNT_DETACH) { if (flags & MNT_DETACH || !propagate_mount_busy(mnt, 2)) {
if (!list_empty(&mnt->mnt_list)) if (!list_empty(&mnt->mnt_list))
umount_tree(mnt, &umount_list); umount_tree(mnt, 1, &umount_list);
retval = 0; retval = 0;
} }
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
...@@ -651,7 +671,7 @@ struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry, ...@@ -651,7 +671,7 @@ struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry,
if (res) { if (res) {
LIST_HEAD(umount_list); LIST_HEAD(umount_list);
spin_lock(&vfsmount_lock); spin_lock(&vfsmount_lock);
umount_tree(res, &umount_list); umount_tree(res, 0, &umount_list);
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
release_mounts(&umount_list); release_mounts(&umount_list);
} }
...@@ -827,7 +847,7 @@ static int do_loopback(struct nameidata *nd, char *old_name, int recurse) ...@@ -827,7 +847,7 @@ static int do_loopback(struct nameidata *nd, char *old_name, int recurse)
if (err) { if (err) {
LIST_HEAD(umount_list); LIST_HEAD(umount_list);
spin_lock(&vfsmount_lock); spin_lock(&vfsmount_lock);
umount_tree(mnt, &umount_list); umount_tree(mnt, 0, &umount_list);
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
release_mounts(&umount_list); release_mounts(&umount_list);
} }
...@@ -1023,12 +1043,12 @@ static void expire_mount(struct vfsmount *mnt, struct list_head *mounts, ...@@ -1023,12 +1043,12 @@ static void expire_mount(struct vfsmount *mnt, struct list_head *mounts,
* Check that it is still dead: the count should now be 2 - as * Check that it is still dead: the count should now be 2 - as
* contributed by the vfsmount parent and the mntget above * contributed by the vfsmount parent and the mntget above
*/ */
if (atomic_read(&mnt->mnt_count) == 2) { if (!propagate_mount_busy(mnt, 2)) {
/* delete from the namespace */ /* delete from the namespace */
touch_namespace(mnt->mnt_namespace); touch_namespace(mnt->mnt_namespace);
list_del_init(&mnt->mnt_list); list_del_init(&mnt->mnt_list);
mnt->mnt_namespace = NULL; mnt->mnt_namespace = NULL;
umount_tree(mnt, umounts); umount_tree(mnt, 1, umounts);
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
} else { } else {
/* /*
...@@ -1647,7 +1667,7 @@ void __put_namespace(struct namespace *namespace) ...@@ -1647,7 +1667,7 @@ void __put_namespace(struct namespace *namespace)
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
down_write(&namespace_sem); down_write(&namespace_sem);
spin_lock(&vfsmount_lock); spin_lock(&vfsmount_lock);
umount_tree(root, &umount_list); umount_tree(root, 0, &umount_list);
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
up_write(&namespace_sem); up_write(&namespace_sem);
release_mounts(&umount_list); release_mounts(&umount_list);
......
...@@ -99,9 +99,94 @@ int propagate_mnt(struct vfsmount *dest_mnt, struct dentry *dest_dentry, ...@@ -99,9 +99,94 @@ int propagate_mnt(struct vfsmount *dest_mnt, struct dentry *dest_dentry,
while (!list_empty(&tmp_list)) { while (!list_empty(&tmp_list)) {
child = list_entry(tmp_list.next, struct vfsmount, mnt_hash); child = list_entry(tmp_list.next, struct vfsmount, mnt_hash);
list_del_init(&child->mnt_hash); list_del_init(&child->mnt_hash);
umount_tree(child, &umount_list); umount_tree(child, 0, &umount_list);
} }
spin_unlock(&vfsmount_lock); spin_unlock(&vfsmount_lock);
release_mounts(&umount_list); release_mounts(&umount_list);
return ret; return ret;
} }
/*
* return true if the refcount is greater than count
*/
static inline int do_refcount_check(struct vfsmount *mnt, int count)
{
int mycount = atomic_read(&mnt->mnt_count);
return (mycount > count);
}
/*
* check if the mount 'mnt' can be unmounted successfully.
* @mnt: the mount to be checked for unmount
* NOTE: unmounting 'mnt' would naturally propagate to all
* other mounts its parent propagates to.
* Check if any of these mounts that **do not have submounts**
* have more references than 'refcnt'. If so return busy.
*/
int propagate_mount_busy(struct vfsmount *mnt, int refcnt)
{
struct vfsmount *m, *child;
struct vfsmount *parent = mnt->mnt_parent;
int ret = 0;
if (mnt == parent)
return do_refcount_check(mnt, refcnt);
/*
* quickly check if the current mount can be unmounted.
* If not, we don't have to go checking for all other
* mounts
*/
if (!list_empty(&mnt->mnt_mounts) || do_refcount_check(mnt, refcnt))
return 1;
for (m = propagation_next(parent, parent); m;
m = propagation_next(m, parent)) {
child = __lookup_mnt(m, mnt->mnt_mountpoint, 0);
if (child && list_empty(&child->mnt_mounts) &&
(ret = do_refcount_check(child, 1)))
break;
}
return ret;
}
/*
* NOTE: unmounting 'mnt' naturally propagates to all other mounts its
* parent propagates to.
*/
static void __propagate_umount(struct vfsmount *mnt)
{
struct vfsmount *parent = mnt->mnt_parent;
struct vfsmount *m;
BUG_ON(parent == mnt);
for (m = propagation_next(parent, parent); m;
m = propagation_next(m, parent)) {
struct vfsmount *child = __lookup_mnt(m,
mnt->mnt_mountpoint, 0);
/*
* umount the child only if the child has no
* other children
*/
if (child && list_empty(&child->mnt_mounts)) {
list_del(&child->mnt_hash);
list_add_tail(&child->mnt_hash, &mnt->mnt_hash);
}
}
}
/*
* collect all mounts that receive propagation from the mount in @list,
* and return these additional mounts in the same list.
* @list: the list of mounts to be unmounted.
*/
int propagate_umount(struct list_head *list)
{
struct vfsmount *mnt;
list_for_each_entry(mnt, list, mnt_hash)
__propagate_umount(mnt);
return 0;
}
...@@ -29,4 +29,6 @@ static inline void set_mnt_shared(struct vfsmount *mnt) ...@@ -29,4 +29,6 @@ static inline void set_mnt_shared(struct vfsmount *mnt)
void change_mnt_propagation(struct vfsmount *, int); void change_mnt_propagation(struct vfsmount *, int);
int propagate_mnt(struct vfsmount *, struct dentry *, struct vfsmount *, int propagate_mnt(struct vfsmount *, struct dentry *, struct vfsmount *,
struct list_head *); struct list_head *);
int propagate_umount(struct list_head *);
int propagate_mount_busy(struct vfsmount *, int);
#endif /* _LINUX_PNODE_H */ #endif /* _LINUX_PNODE_H */
...@@ -329,6 +329,7 @@ static inline int d_mountpoint(struct dentry *dentry) ...@@ -329,6 +329,7 @@ static inline int d_mountpoint(struct dentry *dentry)
} }
extern struct vfsmount *lookup_mnt(struct vfsmount *, struct dentry *); extern struct vfsmount *lookup_mnt(struct vfsmount *, struct dentry *);
extern struct vfsmount *__lookup_mnt(struct vfsmount *, struct dentry *, int);
extern struct dentry *lookup_create(struct nameidata *nd, int is_dir); extern struct dentry *lookup_create(struct nameidata *nd, int is_dir);
extern int sysctl_vfs_cache_pressure; extern int sysctl_vfs_cache_pressure;
......
...@@ -1251,7 +1251,7 @@ extern int unregister_filesystem(struct file_system_type *); ...@@ -1251,7 +1251,7 @@ 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 umount_tree(struct vfsmount *, int, struct list_head *);
extern void release_mounts(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 struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment