diff options
author | Jan Kara <jack@suse.com> | 2015-09-04 18:43:12 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-09-04 19:54:41 -0400 |
commit | 4712e722f91457e60723b9cef6265a74290efba9 (patch) | |
tree | b6b4bf48018d23c180cb7407ea1ff58402664e5c | |
parent | 925d1132a03e33cb8f29a0057300d023b4f1be23 (diff) |
fsnotify: get rid of fsnotify_destroy_mark_locked()
fsnotify_destroy_mark_locked() is subtle to use because it temporarily
releases group->mark_mutex. To avoid future problems with this
function, split it into two.
fsnotify_detach_mark() is the part that needs group->mark_mutex and
fsnotify_free_mark() is the part that must be called outside of
group->mark_mutex. This way it's much clearer what's going on and we
also avoid some pointless acquisitions of group->mark_mutex.
Signed-off-by: Jan Kara <jack@suse.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | fs/notify/dnotify/dnotify.c | 14 | ||||
-rw-r--r-- | fs/notify/fanotify/fanotify_user.c | 8 | ||||
-rw-r--r-- | fs/notify/mark.c | 73 | ||||
-rw-r--r-- | include/linux/fsnotify_backend.h | 7 |
4 files changed, 61 insertions, 41 deletions
diff --git a/fs/notify/dnotify/dnotify.c b/fs/notify/dnotify/dnotify.c index 44523f4a6084..6faaf710e563 100644 --- a/fs/notify/dnotify/dnotify.c +++ b/fs/notify/dnotify/dnotify.c | |||
@@ -154,6 +154,7 @@ void dnotify_flush(struct file *filp, fl_owner_t id) | |||
154 | struct dnotify_struct *dn; | 154 | struct dnotify_struct *dn; |
155 | struct dnotify_struct **prev; | 155 | struct dnotify_struct **prev; |
156 | struct inode *inode; | 156 | struct inode *inode; |
157 | bool free = false; | ||
157 | 158 | ||
158 | inode = file_inode(filp); | 159 | inode = file_inode(filp); |
159 | if (!S_ISDIR(inode->i_mode)) | 160 | if (!S_ISDIR(inode->i_mode)) |
@@ -182,11 +183,15 @@ void dnotify_flush(struct file *filp, fl_owner_t id) | |||
182 | 183 | ||
183 | /* nothing else could have found us thanks to the dnotify_groups | 184 | /* nothing else could have found us thanks to the dnotify_groups |
184 | mark_mutex */ | 185 | mark_mutex */ |
185 | if (dn_mark->dn == NULL) | 186 | if (dn_mark->dn == NULL) { |
186 | fsnotify_destroy_mark_locked(fsn_mark, dnotify_group); | 187 | fsnotify_detach_mark(fsn_mark); |
188 | free = true; | ||
189 | } | ||
187 | 190 | ||
188 | mutex_unlock(&dnotify_group->mark_mutex); | 191 | mutex_unlock(&dnotify_group->mark_mutex); |
189 | 192 | ||
193 | if (free) | ||
194 | fsnotify_free_mark(fsn_mark); | ||
190 | fsnotify_put_mark(fsn_mark); | 195 | fsnotify_put_mark(fsn_mark); |
191 | } | 196 | } |
192 | 197 | ||
@@ -362,9 +367,10 @@ out: | |||
362 | spin_unlock(&fsn_mark->lock); | 367 | spin_unlock(&fsn_mark->lock); |
363 | 368 | ||
364 | if (destroy) | 369 | if (destroy) |
365 | fsnotify_destroy_mark_locked(fsn_mark, dnotify_group); | 370 | fsnotify_detach_mark(fsn_mark); |
366 | |||
367 | mutex_unlock(&dnotify_group->mark_mutex); | 371 | mutex_unlock(&dnotify_group->mark_mutex); |
372 | if (destroy) | ||
373 | fsnotify_free_mark(fsn_mark); | ||
368 | fsnotify_put_mark(fsn_mark); | 374 | fsnotify_put_mark(fsn_mark); |
369 | out_err: | 375 | out_err: |
370 | if (new_fsn_mark) | 376 | if (new_fsn_mark) |
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index cf275500a665..8e8e6bcd1d43 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c | |||
@@ -529,8 +529,10 @@ static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group, | |||
529 | removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags, | 529 | removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags, |
530 | &destroy_mark); | 530 | &destroy_mark); |
531 | if (destroy_mark) | 531 | if (destroy_mark) |
532 | fsnotify_destroy_mark_locked(fsn_mark, group); | 532 | fsnotify_detach_mark(fsn_mark); |
533 | mutex_unlock(&group->mark_mutex); | 533 | mutex_unlock(&group->mark_mutex); |
534 | if (destroy_mark) | ||
535 | fsnotify_free_mark(fsn_mark); | ||
534 | 536 | ||
535 | fsnotify_put_mark(fsn_mark); | 537 | fsnotify_put_mark(fsn_mark); |
536 | if (removed & real_mount(mnt)->mnt_fsnotify_mask) | 538 | if (removed & real_mount(mnt)->mnt_fsnotify_mask) |
@@ -557,8 +559,10 @@ static int fanotify_remove_inode_mark(struct fsnotify_group *group, | |||
557 | removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags, | 559 | removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags, |
558 | &destroy_mark); | 560 | &destroy_mark); |
559 | if (destroy_mark) | 561 | if (destroy_mark) |
560 | fsnotify_destroy_mark_locked(fsn_mark, group); | 562 | fsnotify_detach_mark(fsn_mark); |
561 | mutex_unlock(&group->mark_mutex); | 563 | mutex_unlock(&group->mark_mutex); |
564 | if (destroy_mark) | ||
565 | fsnotify_free_mark(fsn_mark); | ||
562 | 566 | ||
563 | /* matches the fsnotify_find_inode_mark() */ | 567 | /* matches the fsnotify_find_inode_mark() */ |
564 | fsnotify_put_mark(fsn_mark); | 568 | fsnotify_put_mark(fsn_mark); |
diff --git a/fs/notify/mark.c b/fs/notify/mark.c index 3b2d1ba41e7b..fc0df4442f7b 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c | |||
@@ -122,26 +122,27 @@ u32 fsnotify_recalc_mask(struct hlist_head *head) | |||
122 | } | 122 | } |
123 | 123 | ||
124 | /* | 124 | /* |
125 | * Any time a mark is getting freed we end up here. | 125 | * Remove mark from inode / vfsmount list, group list, drop inode reference |
126 | * The caller had better be holding a reference to this mark so we don't actually | 126 | * if we got one. |
127 | * do the final put under the mark->lock | 127 | * |
128 | * Must be called with group->mark_mutex held. | ||
128 | */ | 129 | */ |
129 | void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, | 130 | void fsnotify_detach_mark(struct fsnotify_mark *mark) |
130 | struct fsnotify_group *group) | ||
131 | { | 131 | { |
132 | struct inode *inode = NULL; | 132 | struct inode *inode = NULL; |
133 | struct fsnotify_group *group = mark->group; | ||
133 | 134 | ||
134 | BUG_ON(!mutex_is_locked(&group->mark_mutex)); | 135 | BUG_ON(!mutex_is_locked(&group->mark_mutex)); |
135 | 136 | ||
136 | spin_lock(&mark->lock); | 137 | spin_lock(&mark->lock); |
137 | 138 | ||
138 | /* something else already called this function on this mark */ | 139 | /* something else already called this function on this mark */ |
139 | if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE)) { | 140 | if (!(mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED)) { |
140 | spin_unlock(&mark->lock); | 141 | spin_unlock(&mark->lock); |
141 | return; | 142 | return; |
142 | } | 143 | } |
143 | 144 | ||
144 | mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE; | 145 | mark->flags &= ~FSNOTIFY_MARK_FLAG_ATTACHED; |
145 | 146 | ||
146 | if (mark->flags & FSNOTIFY_MARK_FLAG_INODE) { | 147 | if (mark->flags & FSNOTIFY_MARK_FLAG_INODE) { |
147 | inode = mark->inode; | 148 | inode = mark->inode; |
@@ -150,6 +151,12 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, | |||
150 | fsnotify_destroy_vfsmount_mark(mark); | 151 | fsnotify_destroy_vfsmount_mark(mark); |
151 | else | 152 | else |
152 | BUG(); | 153 | BUG(); |
154 | /* | ||
155 | * Note that we didn't update flags telling whether inode cares about | ||
156 | * what's happening with children. We update these flags from | ||
157 | * __fsnotify_parent() lazily when next event happens on one of our | ||
158 | * children. | ||
159 | */ | ||
153 | 160 | ||
154 | list_del_init(&mark->g_list); | 161 | list_del_init(&mark->g_list); |
155 | 162 | ||
@@ -157,18 +164,32 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, | |||
157 | 164 | ||
158 | if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED)) | 165 | if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED)) |
159 | iput(inode); | 166 | iput(inode); |
160 | /* release lock temporarily */ | 167 | |
161 | mutex_unlock(&group->mark_mutex); | 168 | atomic_dec(&group->num_marks); |
169 | } | ||
170 | |||
171 | /* | ||
172 | * Free fsnotify mark. The freeing is actually happening from a kthread which | ||
173 | * first waits for srcu period end. Caller must have a reference to the mark | ||
174 | * or be protected by fsnotify_mark_srcu. | ||
175 | */ | ||
176 | void fsnotify_free_mark(struct fsnotify_mark *mark) | ||
177 | { | ||
178 | struct fsnotify_group *group = mark->group; | ||
179 | |||
180 | spin_lock(&mark->lock); | ||
181 | /* something else already called this function on this mark */ | ||
182 | if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE)) { | ||
183 | spin_unlock(&mark->lock); | ||
184 | return; | ||
185 | } | ||
186 | mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE; | ||
187 | spin_unlock(&mark->lock); | ||
162 | 188 | ||
163 | spin_lock(&destroy_lock); | 189 | spin_lock(&destroy_lock); |
164 | list_add(&mark->g_list, &destroy_list); | 190 | list_add(&mark->g_list, &destroy_list); |
165 | spin_unlock(&destroy_lock); | 191 | spin_unlock(&destroy_lock); |
166 | wake_up(&destroy_waitq); | 192 | wake_up(&destroy_waitq); |
167 | /* | ||
168 | * We don't necessarily have a ref on mark from caller so the above destroy | ||
169 | * may have actually freed it, unless this group provides a 'freeing_mark' | ||
170 | * function which must be holding a reference. | ||
171 | */ | ||
172 | 193 | ||
173 | /* | 194 | /* |
174 | * Some groups like to know that marks are being freed. This is a | 195 | * Some groups like to know that marks are being freed. This is a |
@@ -177,30 +198,15 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, | |||
177 | */ | 198 | */ |
178 | if (group->ops->freeing_mark) | 199 | if (group->ops->freeing_mark) |
179 | group->ops->freeing_mark(mark, group); | 200 | group->ops->freeing_mark(mark, group); |
180 | |||
181 | /* | ||
182 | * __fsnotify_update_child_dentry_flags(inode); | ||
183 | * | ||
184 | * I really want to call that, but we can't, we have no idea if the inode | ||
185 | * still exists the second we drop the mark->lock. | ||
186 | * | ||
187 | * The next time an event arrive to this inode from one of it's children | ||
188 | * __fsnotify_parent will see that the inode doesn't care about it's | ||
189 | * children and will update all of these flags then. So really this | ||
190 | * is just a lazy update (and could be a perf win...) | ||
191 | */ | ||
192 | |||
193 | atomic_dec(&group->num_marks); | ||
194 | |||
195 | mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); | ||
196 | } | 201 | } |
197 | 202 | ||
198 | void fsnotify_destroy_mark(struct fsnotify_mark *mark, | 203 | void fsnotify_destroy_mark(struct fsnotify_mark *mark, |
199 | struct fsnotify_group *group) | 204 | struct fsnotify_group *group) |
200 | { | 205 | { |
201 | mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); | 206 | mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); |
202 | fsnotify_destroy_mark_locked(mark, group); | 207 | fsnotify_detach_mark(mark); |
203 | mutex_unlock(&group->mark_mutex); | 208 | mutex_unlock(&group->mark_mutex); |
209 | fsnotify_free_mark(mark); | ||
204 | } | 210 | } |
205 | 211 | ||
206 | void fsnotify_destroy_marks(struct hlist_head *head, spinlock_t *lock) | 212 | void fsnotify_destroy_marks(struct hlist_head *head, spinlock_t *lock) |
@@ -342,7 +348,7 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark, | |||
342 | * inode->i_lock | 348 | * inode->i_lock |
343 | */ | 349 | */ |
344 | spin_lock(&mark->lock); | 350 | spin_lock(&mark->lock); |
345 | mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE; | 351 | mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE | FSNOTIFY_MARK_FLAG_ATTACHED; |
346 | 352 | ||
347 | fsnotify_get_group(group); | 353 | fsnotify_get_group(group); |
348 | mark->group = group; | 354 | mark->group = group; |
@@ -448,8 +454,9 @@ void fsnotify_clear_marks_by_group_flags(struct fsnotify_group *group, | |||
448 | } | 454 | } |
449 | mark = list_first_entry(&to_free, struct fsnotify_mark, g_list); | 455 | mark = list_first_entry(&to_free, struct fsnotify_mark, g_list); |
450 | fsnotify_get_mark(mark); | 456 | fsnotify_get_mark(mark); |
451 | fsnotify_destroy_mark_locked(mark, group); | 457 | fsnotify_detach_mark(mark); |
452 | mutex_unlock(&group->mark_mutex); | 458 | mutex_unlock(&group->mark_mutex); |
459 | fsnotify_free_mark(mark); | ||
453 | fsnotify_put_mark(mark); | 460 | fsnotify_put_mark(mark); |
454 | } | 461 | } |
455 | } | 462 | } |
diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index f044fe30e8c3..e0727d77feaf 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h | |||
@@ -236,6 +236,7 @@ struct fsnotify_mark { | |||
236 | #define FSNOTIFY_MARK_FLAG_OBJECT_PINNED 0x04 | 236 | #define FSNOTIFY_MARK_FLAG_OBJECT_PINNED 0x04 |
237 | #define FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY 0x08 | 237 | #define FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY 0x08 |
238 | #define FSNOTIFY_MARK_FLAG_ALIVE 0x10 | 238 | #define FSNOTIFY_MARK_FLAG_ALIVE 0x10 |
239 | #define FSNOTIFY_MARK_FLAG_ATTACHED 0x20 | ||
239 | unsigned int flags; /* flags [mark->lock] */ | 240 | unsigned int flags; /* flags [mark->lock] */ |
240 | void (*free_mark)(struct fsnotify_mark *mark); /* called on final put+free */ | 241 | void (*free_mark)(struct fsnotify_mark *mark); /* called on final put+free */ |
241 | }; | 242 | }; |
@@ -353,8 +354,10 @@ extern int fsnotify_add_mark_locked(struct fsnotify_mark *mark, struct fsnotify_ | |||
353 | /* given a group and a mark, flag mark to be freed when all references are dropped */ | 354 | /* given a group and a mark, flag mark to be freed when all references are dropped */ |
354 | extern void fsnotify_destroy_mark(struct fsnotify_mark *mark, | 355 | extern void fsnotify_destroy_mark(struct fsnotify_mark *mark, |
355 | struct fsnotify_group *group); | 356 | struct fsnotify_group *group); |
356 | extern void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, | 357 | /* detach mark from inode / mount list, group list, drop inode reference */ |
357 | struct fsnotify_group *group); | 358 | extern void fsnotify_detach_mark(struct fsnotify_mark *mark); |
359 | /* free mark */ | ||
360 | extern void fsnotify_free_mark(struct fsnotify_mark *mark); | ||
358 | /* run all the marks in a group, and clear all of the vfsmount marks */ | 361 | /* run all the marks in a group, and clear all of the vfsmount marks */ |
359 | extern void fsnotify_clear_vfsmount_marks_by_group(struct fsnotify_group *group); | 362 | extern void fsnotify_clear_vfsmount_marks_by_group(struct fsnotify_group *group); |
360 | /* run all the marks in a group, and clear all of the inode marks */ | 363 | /* run all the marks in a group, and clear all of the inode marks */ |