Commit a05964f3917c7c55368c229d7985f8e7c9977e97
Committed by
Linus Torvalds
1 parent
2144440327
Exists in
master
and in
40 other branches
[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
fs/namespace.c
... | ... | @@ -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); |
fs/pnode.c
... | ... | @@ -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 | } |
fs/pnode.h
... | ... | @@ -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; |
include/linux/fs.h
... | ... | @@ -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); |