Commit a05964f3917c7c55368c229d7985f8e7c9977e97

Authored by Ram Pai
Committed by Linus Torvalds
1 parent 2144440327

[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: Ram Pai <linuxram@us.ibm.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

Showing 5 changed files with 128 additions and 20 deletions Side-by-side Diff

... ... @@ -86,31 +86,44 @@
86 86 }
87 87  
88 88 /*
89   - * Now, lookup_mnt increments the ref count before returning
90   - * the vfsmount struct.
  89 + * find the first or last mount at @dentry on vfsmount @mnt depending on
  90 + * @dir. If @dir is set return the first mount else return the last mount.
91 91 */
92   -struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
  92 +struct vfsmount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry,
  93 + int dir)
93 94 {
94 95 struct list_head *head = mount_hashtable + hash(mnt, dentry);
95 96 struct list_head *tmp = head;
96 97 struct vfsmount *p, *found = NULL;
97 98  
98   - spin_lock(&vfsmount_lock);
99 99 for (;;) {
100   - tmp = tmp->next;
  100 + tmp = dir ? tmp->next : tmp->prev;
101 101 p = NULL;
102 102 if (tmp == head)
103 103 break;
104 104 p = list_entry(tmp, struct vfsmount, mnt_hash);
105 105 if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
106   - found = mntget(p);
  106 + found = p;
107 107 break;
108 108 }
109 109 }
110   - spin_unlock(&vfsmount_lock);
111 110 return found;
112 111 }
113 112  
  113 +/*
  114 + * lookup_mnt increments the ref count before returning
  115 + * the vfsmount struct.
  116 + */
  117 +struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
  118 +{
  119 + struct vfsmount *child_mnt;
  120 + spin_lock(&vfsmount_lock);
  121 + if ((child_mnt = __lookup_mnt(mnt, dentry, 1)))
  122 + mntget(child_mnt);
  123 + spin_unlock(&vfsmount_lock);
  124 + return child_mnt;
  125 +}
  126 +
114 127 static inline int check_mnt(struct vfsmount *mnt)
115 128 {
116 129 return mnt->mnt_namespace == current->namespace;
... ... @@ -404,9 +417,12 @@
404 417 */
405 418 int may_umount(struct vfsmount *mnt)
406 419 {
407   - if (atomic_read(&mnt->mnt_count) > 2)
408   - return -EBUSY;
409   - return 0;
  420 + int ret = 0;
  421 + spin_lock(&vfsmount_lock);
  422 + if (propagate_mount_busy(mnt, 2))
  423 + ret = -EBUSY;
  424 + spin_unlock(&vfsmount_lock);
  425 + return ret;
410 426 }
411 427  
412 428 EXPORT_SYMBOL(may_umount);
... ... @@ -433,7 +449,7 @@
433 449 }
434 450 }
435 451  
436   -void umount_tree(struct vfsmount *mnt, struct list_head *kill)
  452 +void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill)
437 453 {
438 454 struct vfsmount *p;
439 455  
... ... @@ -442,6 +458,9 @@
442 458 list_add(&p->mnt_hash, kill);
443 459 }
444 460  
  461 + if (propagate)
  462 + propagate_umount(kill);
  463 +
445 464 list_for_each_entry(p, kill, mnt_hash) {
446 465 list_del_init(&p->mnt_expire);
447 466 list_del_init(&p->mnt_list);
... ... @@ -450,6 +469,7 @@
450 469 list_del_init(&p->mnt_child);
451 470 if (p->mnt_parent != p)
452 471 mnt->mnt_mountpoint->d_mounted--;
  472 + change_mnt_propagation(p, MS_PRIVATE);
453 473 }
454 474 }
455 475  
456 476  
... ... @@ -526,9 +546,9 @@
526 546 event++;
527 547  
528 548 retval = -EBUSY;
529   - if (atomic_read(&mnt->mnt_count) == 2 || flags & MNT_DETACH) {
  549 + if (flags & MNT_DETACH || !propagate_mount_busy(mnt, 2)) {
530 550 if (!list_empty(&mnt->mnt_list))
531   - umount_tree(mnt, &umount_list);
  551 + umount_tree(mnt, 1, &umount_list);
532 552 retval = 0;
533 553 }
534 554 spin_unlock(&vfsmount_lock);
... ... @@ -651,7 +671,7 @@
651 671 if (res) {
652 672 LIST_HEAD(umount_list);
653 673 spin_lock(&vfsmount_lock);
654   - umount_tree(res, &umount_list);
  674 + umount_tree(res, 0, &umount_list);
655 675 spin_unlock(&vfsmount_lock);
656 676 release_mounts(&umount_list);
657 677 }
... ... @@ -827,7 +847,7 @@
827 847 if (err) {
828 848 LIST_HEAD(umount_list);
829 849 spin_lock(&vfsmount_lock);
830   - umount_tree(mnt, &umount_list);
  850 + umount_tree(mnt, 0, &umount_list);
831 851 spin_unlock(&vfsmount_lock);
832 852 release_mounts(&umount_list);
833 853 }
834 854  
... ... @@ -1023,12 +1043,12 @@
1023 1043 * Check that it is still dead: the count should now be 2 - as
1024 1044 * contributed by the vfsmount parent and the mntget above
1025 1045 */
1026   - if (atomic_read(&mnt->mnt_count) == 2) {
  1046 + if (!propagate_mount_busy(mnt, 2)) {
1027 1047 /* delete from the namespace */
1028 1048 touch_namespace(mnt->mnt_namespace);
1029 1049 list_del_init(&mnt->mnt_list);
1030 1050 mnt->mnt_namespace = NULL;
1031   - umount_tree(mnt, umounts);
  1051 + umount_tree(mnt, 1, umounts);
1032 1052 spin_unlock(&vfsmount_lock);
1033 1053 } else {
1034 1054 /*
... ... @@ -1647,7 +1667,7 @@
1647 1667 spin_unlock(&vfsmount_lock);
1648 1668 down_write(&namespace_sem);
1649 1669 spin_lock(&vfsmount_lock);
1650   - umount_tree(root, &umount_list);
  1670 + umount_tree(root, 0, &umount_list);
1651 1671 spin_unlock(&vfsmount_lock);
1652 1672 up_write(&namespace_sem);
1653 1673 release_mounts(&umount_list);
... ... @@ -99,10 +99,95 @@
99 99 while (!list_empty(&tmp_list)) {
100 100 child = list_entry(tmp_list.next, struct vfsmount, mnt_hash);
101 101 list_del_init(&child->mnt_hash);
102   - umount_tree(child, &umount_list);
  102 + umount_tree(child, 0, &umount_list);
103 103 }
104 104 spin_unlock(&vfsmount_lock);
105 105 release_mounts(&umount_list);
106 106 return ret;
  107 +}
  108 +
  109 +/*
  110 + * return true if the refcount is greater than count
  111 + */
  112 +static inline int do_refcount_check(struct vfsmount *mnt, int count)
  113 +{
  114 + int mycount = atomic_read(&mnt->mnt_count);
  115 + return (mycount > count);
  116 +}
  117 +
  118 +/*
  119 + * check if the mount 'mnt' can be unmounted successfully.
  120 + * @mnt: the mount to be checked for unmount
  121 + * NOTE: unmounting 'mnt' would naturally propagate to all
  122 + * other mounts its parent propagates to.
  123 + * Check if any of these mounts that **do not have submounts**
  124 + * have more references than 'refcnt'. If so return busy.
  125 + */
  126 +int propagate_mount_busy(struct vfsmount *mnt, int refcnt)
  127 +{
  128 + struct vfsmount *m, *child;
  129 + struct vfsmount *parent = mnt->mnt_parent;
  130 + int ret = 0;
  131 +
  132 + if (mnt == parent)
  133 + return do_refcount_check(mnt, refcnt);
  134 +
  135 + /*
  136 + * quickly check if the current mount can be unmounted.
  137 + * If not, we don't have to go checking for all other
  138 + * mounts
  139 + */
  140 + if (!list_empty(&mnt->mnt_mounts) || do_refcount_check(mnt, refcnt))
  141 + return 1;
  142 +
  143 + for (m = propagation_next(parent, parent); m;
  144 + m = propagation_next(m, parent)) {
  145 + child = __lookup_mnt(m, mnt->mnt_mountpoint, 0);
  146 + if (child && list_empty(&child->mnt_mounts) &&
  147 + (ret = do_refcount_check(child, 1)))
  148 + break;
  149 + }
  150 + return ret;
  151 +}
  152 +
  153 +/*
  154 + * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  155 + * parent propagates to.
  156 + */
  157 +static void __propagate_umount(struct vfsmount *mnt)
  158 +{
  159 + struct vfsmount *parent = mnt->mnt_parent;
  160 + struct vfsmount *m;
  161 +
  162 + BUG_ON(parent == mnt);
  163 +
  164 + for (m = propagation_next(parent, parent); m;
  165 + m = propagation_next(m, parent)) {
  166 +
  167 + struct vfsmount *child = __lookup_mnt(m,
  168 + mnt->mnt_mountpoint, 0);
  169 + /*
  170 + * umount the child only if the child has no
  171 + * other children
  172 + */
  173 + if (child && list_empty(&child->mnt_mounts)) {
  174 + list_del(&child->mnt_hash);
  175 + list_add_tail(&child->mnt_hash, &mnt->mnt_hash);
  176 + }
  177 + }
  178 +}
  179 +
  180 +/*
  181 + * collect all mounts that receive propagation from the mount in @list,
  182 + * and return these additional mounts in the same list.
  183 + * @list: the list of mounts to be unmounted.
  184 + */
  185 +int propagate_umount(struct list_head *list)
  186 +{
  187 + struct vfsmount *mnt;
  188 +
  189 + list_for_each_entry(mnt, list, mnt_hash)
  190 + __propagate_umount(mnt);
  191 + return 0;
107 192 }
... ... @@ -29,5 +29,7 @@
29 29 void change_mnt_propagation(struct vfsmount *, int);
30 30 int propagate_mnt(struct vfsmount *, struct dentry *, struct vfsmount *,
31 31 struct list_head *);
  32 +int propagate_umount(struct list_head *);
  33 +int propagate_mount_busy(struct vfsmount *, int);
32 34 #endif /* _LINUX_PNODE_H */
include/linux/dcache.h
... ... @@ -329,6 +329,7 @@
329 329 }
330 330  
331 331 extern struct vfsmount *lookup_mnt(struct vfsmount *, struct dentry *);
  332 +extern struct vfsmount *__lookup_mnt(struct vfsmount *, struct dentry *, int);
332 333 extern struct dentry *lookup_create(struct nameidata *nd, int is_dir);
333 334  
334 335 extern int sysctl_vfs_cache_pressure;
... ... @@ -1251,7 +1251,7 @@
1251 1251 extern struct vfsmount *kern_mount(struct file_system_type *);
1252 1252 extern int may_umount_tree(struct vfsmount *);
1253 1253 extern int may_umount(struct vfsmount *);
1254   -extern void umount_tree(struct vfsmount *, struct list_head *);
  1254 +extern void umount_tree(struct vfsmount *, int, struct list_head *);
1255 1255 extern void release_mounts(struct list_head *);
1256 1256 extern long do_mount(char *, char *, char *, unsigned long, void *);
1257 1257 extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);