summaryrefslogtreecommitdiffstats
path: root/fs/notify/mark.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/notify/mark.c')
-rw-r--r--fs/notify/mark.c64
1 files changed, 45 insertions, 19 deletions
diff --git a/fs/notify/mark.c b/fs/notify/mark.c
index 3e594ce4..39ddcaf 100644
--- a/fs/notify/mark.c
+++ b/fs/notify/mark.c
@@ -152,15 +152,31 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark,
BUG();
list_del_init(&mark->g_list);
+
spin_unlock(&mark->lock);
if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED))
iput(inode);
+ /* release lock temporarily */
+ mutex_unlock(&group->mark_mutex);
spin_lock(&destroy_lock);
list_add(&mark->g_list, &destroy_list);
spin_unlock(&destroy_lock);
wake_up(&destroy_waitq);
+ /*
+ * We don't necessarily have a ref on mark from caller so the above destroy
+ * may have actually freed it, unless this group provides a 'freeing_mark'
+ * function which must be holding a reference.
+ */
+
+ /*
+ * Some groups like to know that marks are being freed. This is a
+ * callback to the group function to let it know that this mark
+ * is being freed.
+ */
+ if (group->ops->freeing_mark)
+ group->ops->freeing_mark(mark, group);
/*
* __fsnotify_update_child_dentry_flags(inode);
@@ -175,6 +191,8 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark,
*/
atomic_dec(&group->num_marks);
+
+ mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
}
void fsnotify_destroy_mark(struct fsnotify_mark *mark,
@@ -187,10 +205,7 @@ void fsnotify_destroy_mark(struct fsnotify_mark *mark,
/*
* Destroy all marks in the given list. The marks must be already detached from
- * the original inode / vfsmount. Note that we can race with
- * fsnotify_clear_marks_by_group_flags(). However we hold a reference to each
- * mark so they won't get freed from under us and nobody else touches our
- * free_list list_head.
+ * the original inode / vfsmount.
*/
void fsnotify_destroy_marks(struct list_head *to_free)
{
@@ -391,22 +406,42 @@ struct fsnotify_mark *fsnotify_find_mark(struct hlist_head *head,
}
/*
- * Clear any marks in a group in which mark->flags & flags is true.
+ * clear any marks in a group in which mark->flags & flags is true
*/
void fsnotify_clear_marks_by_group_flags(struct fsnotify_group *group,
unsigned int flags)
{
struct fsnotify_mark *lmark, *mark;
+ LIST_HEAD(to_free);
+ /*
+ * We have to be really careful here. Anytime we drop mark_mutex, e.g.
+ * fsnotify_clear_marks_by_inode() can come and free marks. Even in our
+ * to_free list so we have to use mark_mutex even when accessing that
+ * list. And freeing mark requires us to drop mark_mutex. So we can
+ * reliably free only the first mark in the list. That's why we first
+ * move marks to free to to_free list in one go and then free marks in
+ * to_free list one by one.
+ */
mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
list_for_each_entry_safe(mark, lmark, &group->marks_list, g_list) {
- if (mark->flags & flags) {
- fsnotify_get_mark(mark);
- fsnotify_destroy_mark_locked(mark, group);
- fsnotify_put_mark(mark);
- }
+ if (mark->flags & flags)
+ list_move(&mark->g_list, &to_free);
}
mutex_unlock(&group->mark_mutex);
+
+ while (1) {
+ mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
+ if (list_empty(&to_free)) {
+ mutex_unlock(&group->mark_mutex);
+ break;
+ }
+ mark = list_first_entry(&to_free, struct fsnotify_mark, g_list);
+ fsnotify_get_mark(mark);
+ fsnotify_destroy_mark_locked(mark, group);
+ mutex_unlock(&group->mark_mutex);
+ fsnotify_put_mark(mark);
+ }
}
/*
@@ -445,7 +480,6 @@ static int fsnotify_mark_destroy(void *ignored)
{
struct fsnotify_mark *mark, *next;
struct list_head private_destroy_list;
- struct fsnotify_group *group;
for (;;) {
spin_lock(&destroy_lock);
@@ -457,14 +491,6 @@ static int fsnotify_mark_destroy(void *ignored)
list_for_each_entry_safe(mark, next, &private_destroy_list, g_list) {
list_del_init(&mark->g_list);
- group = mark->group;
- /*
- * Some groups like to know that marks are being freed.
- * This is a callback to the group function to let it
- * know that this mark is being freed.
- */
- if (group && group->ops->freeing_mark)
- group->ops->freeing_mark(mark, group);
fsnotify_put_mark(mark);
}
OpenPOWER on IntegriCloud