diff options
Diffstat (limited to 'fs/kernfs')
-rw-r--r-- | fs/kernfs/dir.c | 129 |
1 files changed, 68 insertions, 61 deletions
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 2193d30156ef..3ac93737174a 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c | |||
@@ -106,18 +106,24 @@ static int kernfs_link_sibling(struct kernfs_node *kn) | |||
106 | * kernfs_unlink_sibling - unlink kernfs_node from sibling rbtree | 106 | * kernfs_unlink_sibling - unlink kernfs_node from sibling rbtree |
107 | * @kn: kernfs_node of interest | 107 | * @kn: kernfs_node of interest |
108 | * | 108 | * |
109 | * Unlink @kn from its sibling rbtree which starts from | 109 | * Try to unlink @kn from its sibling rbtree which starts from |
110 | * kn->parent->dir.children. | 110 | * kn->parent->dir.children. Returns %true if @kn was actually |
111 | * removed, %false if @kn wasn't on the rbtree. | ||
111 | * | 112 | * |
112 | * Locking: | 113 | * Locking: |
113 | * mutex_lock(kernfs_mutex) | 114 | * mutex_lock(kernfs_mutex) |
114 | */ | 115 | */ |
115 | static void kernfs_unlink_sibling(struct kernfs_node *kn) | 116 | static bool kernfs_unlink_sibling(struct kernfs_node *kn) |
116 | { | 117 | { |
118 | if (RB_EMPTY_NODE(&kn->rb)) | ||
119 | return false; | ||
120 | |||
117 | if (kernfs_type(kn) == KERNFS_DIR) | 121 | if (kernfs_type(kn) == KERNFS_DIR) |
118 | kn->parent->dir.subdirs--; | 122 | kn->parent->dir.subdirs--; |
119 | 123 | ||
120 | rb_erase(&kn->rb, &kn->parent->dir.children); | 124 | rb_erase(&kn->rb, &kn->parent->dir.children); |
125 | RB_CLEAR_NODE(&kn->rb); | ||
126 | return true; | ||
121 | } | 127 | } |
122 | 128 | ||
123 | /** | 129 | /** |
@@ -171,26 +177,34 @@ void kernfs_put_active(struct kernfs_node *kn) | |||
171 | * kernfs_deactivate - deactivate kernfs_node | 177 | * kernfs_deactivate - deactivate kernfs_node |
172 | * @kn: kernfs_node to deactivate | 178 | * @kn: kernfs_node to deactivate |
173 | * | 179 | * |
174 | * Deny new active references and drain existing ones. | 180 | * Deny new active references and drain existing ones. Mutiple |
181 | * removers may invoke this function concurrently on @kn and all will | ||
182 | * return after deactivation and draining are complete. | ||
175 | */ | 183 | */ |
176 | static void kernfs_deactivate(struct kernfs_node *kn) | 184 | static void kernfs_deactivate(struct kernfs_node *kn) |
185 | __releases(&kernfs_mutex) __acquires(&kernfs_mutex) | ||
177 | { | 186 | { |
178 | struct kernfs_root *root = kernfs_root(kn); | 187 | struct kernfs_root *root = kernfs_root(kn); |
179 | 188 | ||
189 | lockdep_assert_held(&kernfs_mutex); | ||
180 | BUG_ON(!(kn->flags & KERNFS_REMOVED)); | 190 | BUG_ON(!(kn->flags & KERNFS_REMOVED)); |
181 | 191 | ||
182 | if (!(kernfs_type(kn) & KERNFS_ACTIVE_REF)) | 192 | if (!(kernfs_type(kn) & KERNFS_ACTIVE_REF)) |
183 | return; | 193 | return; |
184 | 194 | ||
185 | if (kn->flags & KERNFS_LOCKDEP) | 195 | /* only the first invocation on @kn should deactivate it */ |
186 | rwsem_acquire(&kn->dep_map, 0, 0, _RET_IP_); | 196 | if (atomic_read(&kn->active) >= 0) |
197 | atomic_add(KN_DEACTIVATED_BIAS, &kn->active); | ||
187 | 198 | ||
188 | atomic_add(KN_DEACTIVATED_BIAS, &kn->active); | 199 | mutex_unlock(&kernfs_mutex); |
189 | 200 | ||
190 | if ((kn->flags & KERNFS_LOCKDEP) && | 201 | if (kn->flags & KERNFS_LOCKDEP) { |
191 | atomic_read(&kn->active) != KN_DEACTIVATED_BIAS) | 202 | rwsem_acquire(&kn->dep_map, 0, 0, _RET_IP_); |
192 | lock_contended(&kn->dep_map, _RET_IP_); | 203 | if (atomic_read(&kn->active) != KN_DEACTIVATED_BIAS) |
204 | lock_contended(&kn->dep_map, _RET_IP_); | ||
205 | } | ||
193 | 206 | ||
207 | /* but everyone should wait for draining */ | ||
194 | wait_event(root->deactivate_waitq, | 208 | wait_event(root->deactivate_waitq, |
195 | atomic_read(&kn->active) == KN_DEACTIVATED_BIAS); | 209 | atomic_read(&kn->active) == KN_DEACTIVATED_BIAS); |
196 | 210 | ||
@@ -198,6 +212,8 @@ static void kernfs_deactivate(struct kernfs_node *kn) | |||
198 | lock_acquired(&kn->dep_map, _RET_IP_); | 212 | lock_acquired(&kn->dep_map, _RET_IP_); |
199 | rwsem_release(&kn->dep_map, 1, _RET_IP_); | 213 | rwsem_release(&kn->dep_map, 1, _RET_IP_); |
200 | } | 214 | } |
215 | |||
216 | mutex_lock(&kernfs_mutex); | ||
201 | } | 217 | } |
202 | 218 | ||
203 | /** | 219 | /** |
@@ -347,6 +363,7 @@ static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root, | |||
347 | 363 | ||
348 | atomic_set(&kn->count, 1); | 364 | atomic_set(&kn->count, 1); |
349 | atomic_set(&kn->active, 0); | 365 | atomic_set(&kn->active, 0); |
366 | RB_CLEAR_NODE(&kn->rb); | ||
350 | 367 | ||
351 | kn->name = name; | 368 | kn->name = name; |
352 | kn->mode = mode; | 369 | kn->mode = mode; |
@@ -454,49 +471,6 @@ int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn) | |||
454 | } | 471 | } |
455 | 472 | ||
456 | /** | 473 | /** |
457 | * kernfs_remove_one - remove kernfs_node from parent | ||
458 | * @acxt: addrm context to use | ||
459 | * @kn: kernfs_node to be removed | ||
460 | * | ||
461 | * Mark @kn removed and drop nlink of parent inode if @kn is a | ||
462 | * directory. @kn is unlinked from the children list. | ||
463 | * | ||
464 | * This function should be called between calls to | ||
465 | * kernfs_addrm_start() and kernfs_addrm_finish() and should be | ||
466 | * passed the same @acxt as passed to kernfs_addrm_start(). | ||
467 | * | ||
468 | * LOCKING: | ||
469 | * Determined by kernfs_addrm_start(). | ||
470 | */ | ||
471 | static void kernfs_remove_one(struct kernfs_addrm_cxt *acxt, | ||
472 | struct kernfs_node *kn) | ||
473 | { | ||
474 | struct kernfs_iattrs *ps_iattr; | ||
475 | |||
476 | /* | ||
477 | * Removal can be called multiple times on the same node. Only the | ||
478 | * first invocation is effective and puts the base ref. | ||
479 | */ | ||
480 | if (kn->flags & KERNFS_REMOVED) | ||
481 | return; | ||
482 | |||
483 | if (kn->parent) { | ||
484 | kernfs_unlink_sibling(kn); | ||
485 | |||
486 | /* Update timestamps on the parent */ | ||
487 | ps_iattr = kn->parent->iattr; | ||
488 | if (ps_iattr) { | ||
489 | ps_iattr->ia_iattr.ia_ctime = CURRENT_TIME; | ||
490 | ps_iattr->ia_iattr.ia_mtime = CURRENT_TIME; | ||
491 | } | ||
492 | } | ||
493 | |||
494 | kn->flags |= KERNFS_REMOVED; | ||
495 | kn->u.removed_list = acxt->removed; | ||
496 | acxt->removed = kn; | ||
497 | } | ||
498 | |||
499 | /** | ||
500 | * kernfs_addrm_finish - finish up kernfs_node add/remove | 474 | * kernfs_addrm_finish - finish up kernfs_node add/remove |
501 | * @acxt: addrm context to finish up | 475 | * @acxt: addrm context to finish up |
502 | * | 476 | * |
@@ -519,7 +493,6 @@ void kernfs_addrm_finish(struct kernfs_addrm_cxt *acxt) | |||
519 | 493 | ||
520 | acxt->removed = kn->u.removed_list; | 494 | acxt->removed = kn->u.removed_list; |
521 | 495 | ||
522 | kernfs_deactivate(kn); | ||
523 | kernfs_unmap_bin_file(kn); | 496 | kernfs_unmap_bin_file(kn); |
524 | kernfs_put(kn); | 497 | kernfs_put(kn); |
525 | } | 498 | } |
@@ -828,20 +801,54 @@ static struct kernfs_node *kernfs_next_descendant_post(struct kernfs_node *pos, | |||
828 | static void __kernfs_remove(struct kernfs_addrm_cxt *acxt, | 801 | static void __kernfs_remove(struct kernfs_addrm_cxt *acxt, |
829 | struct kernfs_node *kn) | 802 | struct kernfs_node *kn) |
830 | { | 803 | { |
831 | struct kernfs_node *pos, *next; | 804 | struct kernfs_node *pos; |
805 | |||
806 | lockdep_assert_held(&kernfs_mutex); | ||
832 | 807 | ||
833 | if (!kn) | 808 | if (!kn) |
834 | return; | 809 | return; |
835 | 810 | ||
836 | pr_debug("kernfs %s: removing\n", kn->name); | 811 | pr_debug("kernfs %s: removing\n", kn->name); |
837 | 812 | ||
838 | next = NULL; | 813 | /* disable lookup and node creation under @kn */ |
814 | pos = NULL; | ||
815 | while ((pos = kernfs_next_descendant_post(pos, kn))) | ||
816 | pos->flags |= KERNFS_REMOVED; | ||
817 | |||
818 | /* deactivate and unlink the subtree node-by-node */ | ||
839 | do { | 819 | do { |
840 | pos = next; | 820 | pos = kernfs_leftmost_descendant(kn); |
841 | next = kernfs_next_descendant_post(pos, kn); | 821 | |
842 | if (pos) | 822 | /* |
843 | kernfs_remove_one(acxt, pos); | 823 | * kernfs_deactivate() drops kernfs_mutex temporarily and |
844 | } while (next); | 824 | * @pos's base ref could have been put by someone else by |
825 | * the time the function returns. Make sure it doesn't go | ||
826 | * away underneath us. | ||
827 | */ | ||
828 | kernfs_get(pos); | ||
829 | |||
830 | kernfs_deactivate(pos); | ||
831 | |||
832 | /* | ||
833 | * kernfs_unlink_sibling() succeeds once per node. Use it | ||
834 | * to decide who's responsible for cleanups. | ||
835 | */ | ||
836 | if (!pos->parent || kernfs_unlink_sibling(pos)) { | ||
837 | struct kernfs_iattrs *ps_iattr = | ||
838 | pos->parent ? pos->parent->iattr : NULL; | ||
839 | |||
840 | /* update timestamps on the parent */ | ||
841 | if (ps_iattr) { | ||
842 | ps_iattr->ia_iattr.ia_ctime = CURRENT_TIME; | ||
843 | ps_iattr->ia_iattr.ia_mtime = CURRENT_TIME; | ||
844 | } | ||
845 | |||
846 | pos->u.removed_list = acxt->removed; | ||
847 | acxt->removed = pos; | ||
848 | } | ||
849 | |||
850 | kernfs_put(pos); | ||
851 | } while (pos != kn); | ||
845 | } | 852 | } |
846 | 853 | ||
847 | /** | 854 | /** |