Commit 4712e722 authored by Jan Kara's avatar Jan Kara Committed by Linus Torvalds

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: default avatarJan Kara <jack@suse.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 925d1132
...@@ -154,6 +154,7 @@ void dnotify_flush(struct file *filp, fl_owner_t id) ...@@ -154,6 +154,7 @@ void dnotify_flush(struct file *filp, fl_owner_t id)
struct dnotify_struct *dn; struct dnotify_struct *dn;
struct dnotify_struct **prev; struct dnotify_struct **prev;
struct inode *inode; struct inode *inode;
bool free = false;
inode = file_inode(filp); inode = file_inode(filp);
if (!S_ISDIR(inode->i_mode)) if (!S_ISDIR(inode->i_mode))
...@@ -182,11 +183,15 @@ void dnotify_flush(struct file *filp, fl_owner_t id) ...@@ -182,11 +183,15 @@ void dnotify_flush(struct file *filp, fl_owner_t id)
/* nothing else could have found us thanks to the dnotify_groups /* nothing else could have found us thanks to the dnotify_groups
mark_mutex */ mark_mutex */
if (dn_mark->dn == NULL) if (dn_mark->dn == NULL) {
fsnotify_destroy_mark_locked(fsn_mark, dnotify_group); fsnotify_detach_mark(fsn_mark);
free = true;
}
mutex_unlock(&dnotify_group->mark_mutex); mutex_unlock(&dnotify_group->mark_mutex);
if (free)
fsnotify_free_mark(fsn_mark);
fsnotify_put_mark(fsn_mark); fsnotify_put_mark(fsn_mark);
} }
...@@ -362,9 +367,10 @@ int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg) ...@@ -362,9 +367,10 @@ int fcntl_dirnotify(int fd, struct file *filp, unsigned long arg)
spin_unlock(&fsn_mark->lock); spin_unlock(&fsn_mark->lock);
if (destroy) if (destroy)
fsnotify_destroy_mark_locked(fsn_mark, dnotify_group); fsnotify_detach_mark(fsn_mark);
mutex_unlock(&dnotify_group->mark_mutex); mutex_unlock(&dnotify_group->mark_mutex);
if (destroy)
fsnotify_free_mark(fsn_mark);
fsnotify_put_mark(fsn_mark); fsnotify_put_mark(fsn_mark);
out_err: out_err:
if (new_fsn_mark) if (new_fsn_mark)
......
...@@ -529,8 +529,10 @@ static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group, ...@@ -529,8 +529,10 @@ static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group,
removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags, removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags,
&destroy_mark); &destroy_mark);
if (destroy_mark) if (destroy_mark)
fsnotify_destroy_mark_locked(fsn_mark, group); fsnotify_detach_mark(fsn_mark);
mutex_unlock(&group->mark_mutex); mutex_unlock(&group->mark_mutex);
if (destroy_mark)
fsnotify_free_mark(fsn_mark);
fsnotify_put_mark(fsn_mark); fsnotify_put_mark(fsn_mark);
if (removed & real_mount(mnt)->mnt_fsnotify_mask) if (removed & real_mount(mnt)->mnt_fsnotify_mask)
...@@ -557,8 +559,10 @@ static int fanotify_remove_inode_mark(struct fsnotify_group *group, ...@@ -557,8 +559,10 @@ static int fanotify_remove_inode_mark(struct fsnotify_group *group,
removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags, removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags,
&destroy_mark); &destroy_mark);
if (destroy_mark) if (destroy_mark)
fsnotify_destroy_mark_locked(fsn_mark, group); fsnotify_detach_mark(fsn_mark);
mutex_unlock(&group->mark_mutex); mutex_unlock(&group->mark_mutex);
if (destroy_mark)
fsnotify_free_mark(fsn_mark);
/* matches the fsnotify_find_inode_mark() */ /* matches the fsnotify_find_inode_mark() */
fsnotify_put_mark(fsn_mark); fsnotify_put_mark(fsn_mark);
......
...@@ -122,26 +122,27 @@ u32 fsnotify_recalc_mask(struct hlist_head *head) ...@@ -122,26 +122,27 @@ u32 fsnotify_recalc_mask(struct hlist_head *head)
} }
/* /*
* Any time a mark is getting freed we end up here. * Remove mark from inode / vfsmount list, group list, drop inode reference
* The caller had better be holding a reference to this mark so we don't actually * if we got one.
* do the final put under the mark->lock *
* Must be called with group->mark_mutex held.
*/ */
void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, void fsnotify_detach_mark(struct fsnotify_mark *mark)
struct fsnotify_group *group)
{ {
struct inode *inode = NULL; struct inode *inode = NULL;
struct fsnotify_group *group = mark->group;
BUG_ON(!mutex_is_locked(&group->mark_mutex)); BUG_ON(!mutex_is_locked(&group->mark_mutex));
spin_lock(&mark->lock); spin_lock(&mark->lock);
/* something else already called this function on this mark */ /* something else already called this function on this mark */
if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE)) { if (!(mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED)) {
spin_unlock(&mark->lock); spin_unlock(&mark->lock);
return; return;
} }
mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE; mark->flags &= ~FSNOTIFY_MARK_FLAG_ATTACHED;
if (mark->flags & FSNOTIFY_MARK_FLAG_INODE) { if (mark->flags & FSNOTIFY_MARK_FLAG_INODE) {
inode = mark->inode; inode = mark->inode;
...@@ -150,6 +151,12 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, ...@@ -150,6 +151,12 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark,
fsnotify_destroy_vfsmount_mark(mark); fsnotify_destroy_vfsmount_mark(mark);
else else
BUG(); BUG();
/*
* Note that we didn't update flags telling whether inode cares about
* what's happening with children. We update these flags from
* __fsnotify_parent() lazily when next event happens on one of our
* children.
*/
list_del_init(&mark->g_list); list_del_init(&mark->g_list);
...@@ -157,18 +164,32 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, ...@@ -157,18 +164,32 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark,
if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED)) if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED))
iput(inode); iput(inode);
/* release lock temporarily */
mutex_unlock(&group->mark_mutex); atomic_dec(&group->num_marks);
}
/*
* Free fsnotify mark. The freeing is actually happening from a kthread which
* first waits for srcu period end. Caller must have a reference to the mark
* or be protected by fsnotify_mark_srcu.
*/
void fsnotify_free_mark(struct fsnotify_mark *mark)
{
struct fsnotify_group *group = mark->group;
spin_lock(&mark->lock);
/* something else already called this function on this mark */
if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE)) {
spin_unlock(&mark->lock);
return;
}
mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE;
spin_unlock(&mark->lock);
spin_lock(&destroy_lock); spin_lock(&destroy_lock);
list_add(&mark->g_list, &destroy_list); list_add(&mark->g_list, &destroy_list);
spin_unlock(&destroy_lock); spin_unlock(&destroy_lock);
wake_up(&destroy_waitq); 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 * 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,30 +198,15 @@ void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark,
*/ */
if (group->ops->freeing_mark) if (group->ops->freeing_mark)
group->ops->freeing_mark(mark, group); group->ops->freeing_mark(mark, group);
/*
* __fsnotify_update_child_dentry_flags(inode);
*
* I really want to call that, but we can't, we have no idea if the inode
* still exists the second we drop the mark->lock.
*
* The next time an event arrive to this inode from one of it's children
* __fsnotify_parent will see that the inode doesn't care about it's
* children and will update all of these flags then. So really this
* is just a lazy update (and could be a perf win...)
*/
atomic_dec(&group->num_marks);
mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
} }
void fsnotify_destroy_mark(struct fsnotify_mark *mark, void fsnotify_destroy_mark(struct fsnotify_mark *mark,
struct fsnotify_group *group) struct fsnotify_group *group)
{ {
mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
fsnotify_destroy_mark_locked(mark, group); fsnotify_detach_mark(mark);
mutex_unlock(&group->mark_mutex); mutex_unlock(&group->mark_mutex);
fsnotify_free_mark(mark);
} }
void fsnotify_destroy_marks(struct hlist_head *head, spinlock_t *lock) 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,7 +348,7 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark,
* inode->i_lock * inode->i_lock
*/ */
spin_lock(&mark->lock); spin_lock(&mark->lock);
mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE; mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE | FSNOTIFY_MARK_FLAG_ATTACHED;
fsnotify_get_group(group); fsnotify_get_group(group);
mark->group = group; mark->group = group;
...@@ -448,8 +454,9 @@ void fsnotify_clear_marks_by_group_flags(struct fsnotify_group *group, ...@@ -448,8 +454,9 @@ void fsnotify_clear_marks_by_group_flags(struct fsnotify_group *group,
} }
mark = list_first_entry(&to_free, struct fsnotify_mark, g_list); mark = list_first_entry(&to_free, struct fsnotify_mark, g_list);
fsnotify_get_mark(mark); fsnotify_get_mark(mark);
fsnotify_destroy_mark_locked(mark, group); fsnotify_detach_mark(mark);
mutex_unlock(&group->mark_mutex); mutex_unlock(&group->mark_mutex);
fsnotify_free_mark(mark);
fsnotify_put_mark(mark); fsnotify_put_mark(mark);
} }
} }
......
...@@ -236,6 +236,7 @@ struct fsnotify_mark { ...@@ -236,6 +236,7 @@ struct fsnotify_mark {
#define FSNOTIFY_MARK_FLAG_OBJECT_PINNED 0x04 #define FSNOTIFY_MARK_FLAG_OBJECT_PINNED 0x04
#define FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY 0x08 #define FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY 0x08
#define FSNOTIFY_MARK_FLAG_ALIVE 0x10 #define FSNOTIFY_MARK_FLAG_ALIVE 0x10
#define FSNOTIFY_MARK_FLAG_ATTACHED 0x20
unsigned int flags; /* flags [mark->lock] */ unsigned int flags; /* flags [mark->lock] */
void (*free_mark)(struct fsnotify_mark *mark); /* called on final put+free */ void (*free_mark)(struct fsnotify_mark *mark); /* called on final put+free */
}; };
...@@ -353,8 +354,10 @@ extern int fsnotify_add_mark_locked(struct fsnotify_mark *mark, struct fsnotify_ ...@@ -353,8 +354,10 @@ extern int fsnotify_add_mark_locked(struct fsnotify_mark *mark, struct fsnotify_
/* given a group and a mark, flag mark to be freed when all references are dropped */ /* given a group and a mark, flag mark to be freed when all references are dropped */
extern void fsnotify_destroy_mark(struct fsnotify_mark *mark, extern void fsnotify_destroy_mark(struct fsnotify_mark *mark,
struct fsnotify_group *group); struct fsnotify_group *group);
extern void fsnotify_destroy_mark_locked(struct fsnotify_mark *mark, /* detach mark from inode / mount list, group list, drop inode reference */
struct fsnotify_group *group); extern void fsnotify_detach_mark(struct fsnotify_mark *mark);
/* free mark */
extern void fsnotify_free_mark(struct fsnotify_mark *mark);
/* run all the marks in a group, and clear all of the vfsmount marks */ /* run all the marks in a group, and clear all of the vfsmount marks */
extern void fsnotify_clear_vfsmount_marks_by_group(struct fsnotify_group *group); extern void fsnotify_clear_vfsmount_marks_by_group(struct fsnotify_group *group);
/* run all the marks in a group, and clear all of the inode marks */ /* run all the marks in a group, and clear all of the inode marks */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment