So far list of marks attached to an object (inode / vfsmount) was protected by i_lock or mnt_root->d_lock. This dictates that the list must be empty before the object can be destroyed although the list is now anchored in the fsnotify_mark_connector structure. Protect the list by a spinlock in the fsnotify_mark_connector structure to decouple lifetime of a list of marks from a lifetime of the object. This also simplifies the code quite a bit since we don't have to differentiate between inode and vfsmount lists in quite a few places anymore. Signed-off-by: Jan Kara <jack@xxxxxxx> --- fs/notify/dnotify/dnotify.c | 3 +- fs/notify/fsnotify.h | 37 +++-------- fs/notify/inode_mark.c | 46 +------------- fs/notify/mark.c | 129 +++++++++++++++++++++++---------------- fs/notify/vfsmount_mark.c | 41 ++----------- include/linux/fsnotify_backend.h | 5 +- 6 files changed, 97 insertions(+), 164 deletions(-) diff --git a/fs/notify/dnotify/dnotify.c b/fs/notify/dnotify/dnotify.c index 3dd2e0ece262..41b2a070761c 100644 --- a/fs/notify/dnotify/dnotify.c +++ b/fs/notify/dnotify/dnotify.c @@ -69,8 +69,7 @@ static void dnotify_recalc_inode_mask(struct fsnotify_mark *fsn_mark) if (old_mask == new_mask) return; - if (fsn_mark->connector->inode) - fsnotify_recalc_inode_mask(fsn_mark->connector->inode); + fsnotify_recalc_mask(fsn_mark->connector); } /* diff --git a/fs/notify/fsnotify.h b/fs/notify/fsnotify.h index 7a95436dc8d3..317ff2327cc8 100644 --- a/fs/notify/fsnotify.h +++ b/fs/notify/fsnotify.h @@ -8,56 +8,35 @@ #include "../mount.h" -struct fsnotify_mark_connector; - /* destroy all events sitting in this groups notification queue */ extern void fsnotify_flush_notify(struct fsnotify_group *group); /* protects reads of inode and vfsmount marks list */ extern struct srcu_struct fsnotify_mark_srcu; -/* Calculate mask of events for a list of marks */ -extern u32 fsnotify_recalc_mask(struct fsnotify_mark_connector *conn); - /* compare two groups for sorting of marks lists */ extern int fsnotify_compare_groups(struct fsnotify_group *a, struct fsnotify_group *b); -extern void fsnotify_set_inode_mark_mask_locked(struct fsnotify_mark *fsn_mark, - __u32 mask); -/* add a mark to an inode */ -extern int fsnotify_add_inode_mark(struct fsnotify_mark *mark, - struct fsnotify_group *group, struct inode *inode, - int allow_dups); -/* add a mark to a vfsmount */ -extern int fsnotify_add_vfsmount_mark(struct fsnotify_mark *mark, - struct fsnotify_group *group, struct vfsmount *mnt, - int allow_dups); - -/* vfsmount specific destruction of a mark */ -extern void fsnotify_destroy_vfsmount_mark(struct fsnotify_mark *mark); -/* inode specific destruction of a mark */ -extern struct inode *fsnotify_destroy_inode_mark(struct fsnotify_mark *mark); -/* Find mark belonging to given group in the list of marks */ -extern struct fsnotify_mark *fsnotify_find_mark(struct hlist_head *head, - struct fsnotify_group *group); -/* Destroy all marks in the given list protected by 'lock' */ -extern void fsnotify_destroy_marks(struct fsnotify_mark_connector **connp, - spinlock_t *lock); +/* Find mark belonging to given group attached to given connector */ +extern struct fsnotify_mark *fsnotify_find_mark( + struct fsnotify_mark_connector **connp, + struct fsnotify_group *group); +/* Destroy all marks connected via given connector */ +extern void fsnotify_destroy_marks(struct fsnotify_mark_connector **connp); /* run the list of all marks associated with inode and destroy them */ static inline void fsnotify_clear_marks_by_inode(struct inode *inode) { if (!inode->i_fsnotify_marks) return; - fsnotify_destroy_marks(&inode->i_fsnotify_marks, &inode->i_lock); + fsnotify_destroy_marks(&inode->i_fsnotify_marks); } /* run the list of all marks associated with vfsmount and destroy them */ static inline void fsnotify_clear_marks_by_mount(struct vfsmount *mnt) { if (!real_mount(mnt)->mnt_fsnotify_marks) return; - fsnotify_destroy_marks(&real_mount(mnt)->mnt_fsnotify_marks, - &mnt->mnt_root->d_lock); + fsnotify_destroy_marks(&real_mount(mnt)->mnt_fsnotify_marks); } /* prepare for freeing all marks associated with given group */ extern void fsnotify_detach_group_marks(struct fsnotify_group *group); diff --git a/fs/notify/inode_mark.c b/fs/notify/inode_mark.c index 564641becb39..b9370316727e 100644 --- a/fs/notify/inode_mark.c +++ b/fs/notify/inode_mark.c @@ -30,42 +30,9 @@ #include "../internal.h" -/* - * Recalculate the inode->i_fsnotify_mask, or the mask of all FS_* event types - * any notifier is interested in hearing for this inode. - */ void fsnotify_recalc_inode_mask(struct inode *inode) { - spin_lock(&inode->i_lock); - inode->i_fsnotify_mask = fsnotify_recalc_mask(inode->i_fsnotify_marks); - spin_unlock(&inode->i_lock); - - __fsnotify_update_child_dentry_flags(inode); -} - -struct inode *fsnotify_destroy_inode_mark(struct fsnotify_mark *mark) -{ - struct inode *inode = mark->connector->inode; - bool empty; - - BUG_ON(!mutex_is_locked(&mark->group->mark_mutex)); - assert_spin_locked(&mark->lock); - - spin_lock(&inode->i_lock); - - hlist_del_init_rcu(&mark->obj_list); - empty = hlist_empty(&mark->connector->list); - mark->connector = NULL; - - /* - * this mark is now off the inode->i_fsnotify_marks list and we - * hold the inode->i_lock, so this is the perfect time to update the - * inode->i_fsnotify_mask - */ - inode->i_fsnotify_mask = fsnotify_recalc_mask(inode->i_fsnotify_marks); - spin_unlock(&inode->i_lock); - - return empty ? inode : NULL; + fsnotify_recalc_mask(inode->i_fsnotify_marks); } /* @@ -83,16 +50,7 @@ void fsnotify_clear_inode_marks_by_group(struct fsnotify_group *group) struct fsnotify_mark *fsnotify_find_inode_mark(struct fsnotify_group *group, struct inode *inode) { - struct fsnotify_mark *mark; - - if (!inode->i_fsnotify_marks) - return NULL; - - spin_lock(&inode->i_lock); - mark = fsnotify_find_mark(&inode->i_fsnotify_marks->list, group); - spin_unlock(&inode->i_lock); - - return mark; + return fsnotify_find_mark(&inode->i_fsnotify_marks, group); } /** diff --git a/fs/notify/mark.c b/fs/notify/mark.c index f3f6286bf9bb..299d5f0f42d6 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -33,7 +33,7 @@ * * group->mark_mutex * mark->lock - * inode->i_lock + * mark->connector->lock * * group->mark_mutex protects the marks_list anchored inside a given group and * each mark is hooked via the g_list. It also protects the groups private @@ -44,10 +44,12 @@ * is assigned to as well as the access to a reference of the inode/vfsmount * that is being watched by the mark. * - * inode->i_lock protects the i_fsnotify_marks list anchored inside a - * given inode and each mark is hooked via the i_list. (and sorta the - * free_i_list) + * mark->connector->lock protects the list of marks anchored inside an + * inode / vfsmount and each mark is hooked via the i_list. * + * A list of notification marks relating to inode / mnt is contained in + * fsnotify_mark_connector. That structure is alive as long as there are any marks + * in the list and is also protected by fsnotify_mark_srcu. * * LIFETIME: * Inode marks survive between when they are added to an inode and when their @@ -105,15 +107,59 @@ void fsnotify_put_mark(struct fsnotify_mark *mark) } } -/* Calculate mask of events for a list of marks */ -u32 fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) +static void __fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) { u32 new_mask = 0; struct fsnotify_mark *mark; - hlist_for_each_entry(mark, &conn->list, obj_list) - new_mask |= mark->mask; - return new_mask; + assert_spin_locked(&conn->lock); + hlist_for_each_entry(mark, &conn->list, obj_list) { + if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) + new_mask |= mark->mask; + } + if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) + conn->inode->i_fsnotify_mask = new_mask; + else if (conn->flags & FSNOTIFY_OBJ_TYPE_VFSMOUNT) + real_mount(conn->mnt)->mnt_fsnotify_mask = new_mask; +} + +/* + * Calculate mask of events for a list of marks. The caller must make sure list + * cannot disappear under us (usually by holding a mark->lock or + * mark->group->mark_mutex for a mark on this list). + */ +void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) +{ + struct inode *inode = NULL; + + spin_lock(&conn->lock); + __fsnotify_recalc_mask(conn); + if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) + inode = igrab(conn->inode); + spin_unlock(&conn->lock); + if (inode) { + __fsnotify_update_child_dentry_flags(inode); + iput(inode); + } +} + +static struct inode *fsnotify_detach_from_object(struct fsnotify_mark *mark) +{ + struct fsnotify_mark_connector *conn; + struct inode *inode = NULL; + + conn = mark->connector; + spin_lock(&conn->lock); + hlist_del_init_rcu(&mark->obj_list); + if (hlist_empty(&conn->list)) { + if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) + inode = conn->inode; + } else + __fsnotify_recalc_mask(conn); + mark->connector = NULL; + spin_unlock(&conn->lock); + + return inode; } /* @@ -139,12 +185,8 @@ void fsnotify_detach_mark(struct fsnotify_mark *mark) mark->flags &= ~FSNOTIFY_MARK_FLAG_ATTACHED; - if (mark->connector->flags & FSNOTIFY_OBJ_TYPE_INODE) - inode = fsnotify_destroy_inode_mark(mark); - else if (mark->connector->flags & FSNOTIFY_OBJ_TYPE_VFSMOUNT) - fsnotify_destroy_vfsmount_mark(mark); - else - BUG(); + inode = fsnotify_detach_from_object(mark); + /* * Note that we didn't update flags telling whether inode cares about * what's happening with children. We update these flags from @@ -282,23 +324,10 @@ static struct fsnotify_mark_connector *fsnotify_grab_connector( { if (!conn) return NULL; - if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) - spin_lock(&conn->inode->i_lock); - else if (conn->flags & FSNOTIFY_OBJ_TYPE_VFSMOUNT) - spin_lock(&conn->mnt->mnt_root->d_lock); - else - return NULL; + spin_lock(&conn->lock); return conn; } -static void fsnotify_put_connector(struct fsnotify_mark_connector *conn) -{ - if (conn->flags & FSNOTIFY_OBJ_TYPE_INODE) - spin_unlock(&conn->inode->i_lock); - else if (conn->flags & FSNOTIFY_OBJ_TYPE_VFSMOUNT) - spin_unlock(&conn->mnt->mnt_root->d_lock); -} - /* * Add mark into proper place in given list of marks. These marks may be used * for the fsnotify backend to determine which event types should be delivered @@ -329,6 +358,7 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, GFP_KERNEL); if (!conn) return -ENOMEM; + spin_lock_init(&conn->lock); INIT_HLIST_HEAD(&conn->list); if (inode) { conn->flags = FSNOTIFY_OBJ_TYPE_INODE; @@ -373,7 +403,7 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, added: mark->connector = conn; out_err: - fsnotify_put_connector(conn); + spin_unlock(&conn->lock); spin_unlock(&mark->lock); return err; } @@ -397,7 +427,7 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark, * LOCKING ORDER!!!! * group->mark_mutex * mark->lock - * inode->i_lock + * mark->connector->lock */ spin_lock(&mark->lock); mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE | FSNOTIFY_MARK_FLAG_ATTACHED; @@ -413,10 +443,8 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark, if (ret) goto err; - if (inode) - fsnotify_recalc_inode_mask(inode); - else - fsnotify_recalc_vfsmount_mask(mnt); + if (mark->mask) + fsnotify_recalc_mask(mark->connector); return ret; err: @@ -451,17 +479,23 @@ int fsnotify_add_mark(struct fsnotify_mark *mark, struct fsnotify_group *group, * Given a list of marks, find the mark associated with given group. If found * take a reference to that mark and return it, else return NULL. */ -struct fsnotify_mark *fsnotify_find_mark(struct hlist_head *head, +struct fsnotify_mark *fsnotify_find_mark(struct fsnotify_mark_connector **connp, struct fsnotify_group *group) { struct fsnotify_mark *mark; + struct fsnotify_mark_connector *conn; - hlist_for_each_entry(mark, head, obj_list) { + conn = fsnotify_grab_connector(*connp); + if (!conn) + return NULL; + hlist_for_each_entry(mark, &conn->list, obj_list) { if (mark->group == group) { fsnotify_get_mark(mark); + spin_unlock(&conn->lock); return mark; } } + spin_unlock(&conn->lock); return NULL; } @@ -531,12 +565,12 @@ void fsnotify_detach_group_marks(struct fsnotify_group *group) } } -void fsnotify_destroy_marks(struct fsnotify_mark_connector **connp, - spinlock_t *lock) +void fsnotify_destroy_marks(struct fsnotify_mark_connector **connp) { + struct fsnotify_mark_connector *conn; struct fsnotify_mark *mark; - while (1) { + while ((conn = fsnotify_grab_connector(*connp))) { /* * We have to be careful since we can race with e.g. * fsnotify_clear_marks_by_group() and once we drop 'lock', @@ -544,21 +578,14 @@ void fsnotify_destroy_marks(struct fsnotify_mark_connector **connp, * we are holding mark reference so mark cannot be freed and * calling fsnotify_destroy_mark() more than once is fine. */ - spin_lock(lock); - if (hlist_empty(&(*connp)->list)) { - spin_unlock(lock); + if (hlist_empty(&conn->list)) { + spin_unlock(&conn->lock); break; } - mark = hlist_entry((*connp)->list.first, struct fsnotify_mark, + mark = hlist_entry(conn->list.first, struct fsnotify_mark, obj_list); - /* - * We don't update i_fsnotify_mask / mnt_fsnotify_mask here - * since inode / mount is going away anyway. So just remove - * mark from the list. - */ - hlist_del_init_rcu(&mark->obj_list); fsnotify_get_mark(mark); - spin_unlock(lock); + spin_unlock(&conn->lock); fsnotify_destroy_mark(mark, mark->group); fsnotify_put_mark(mark); } diff --git a/fs/notify/vfsmount_mark.c b/fs/notify/vfsmount_mark.c index 9e676b4b0068..8fc1aca21bcf 100644 --- a/fs/notify/vfsmount_mark.c +++ b/fs/notify/vfsmount_mark.c @@ -29,39 +29,14 @@ #include <linux/fsnotify_backend.h> #include "fsnotify.h" -void fsnotify_clear_vfsmount_marks_by_group(struct fsnotify_group *group) -{ - fsnotify_clear_marks_by_group_flags(group, FSNOTIFY_OBJ_TYPE_VFSMOUNT); -} - -/* - * Recalculate the mnt->mnt_fsnotify_mask, or the mask of all FS_* event types - * any notifier is interested in hearing for this mount point - */ void fsnotify_recalc_vfsmount_mask(struct vfsmount *mnt) { - struct mount *m = real_mount(mnt); - - spin_lock(&mnt->mnt_root->d_lock); - m->mnt_fsnotify_mask = fsnotify_recalc_mask(m->mnt_fsnotify_marks); - spin_unlock(&mnt->mnt_root->d_lock); + fsnotify_recalc_mask(real_mount(mnt)->mnt_fsnotify_marks); } -void fsnotify_destroy_vfsmount_mark(struct fsnotify_mark *mark) +void fsnotify_clear_vfsmount_marks_by_group(struct fsnotify_group *group) { - struct vfsmount *mnt = mark->connector->mnt; - struct mount *m = real_mount(mnt); - - BUG_ON(!mutex_is_locked(&mark->group->mark_mutex)); - assert_spin_locked(&mark->lock); - - spin_lock(&mnt->mnt_root->d_lock); - - hlist_del_init_rcu(&mark->obj_list); - mark->connector = NULL; - - m->mnt_fsnotify_mask = fsnotify_recalc_mask(m->mnt_fsnotify_marks); - spin_unlock(&mnt->mnt_root->d_lock); + fsnotify_clear_marks_by_group_flags(group, FSNOTIFY_OBJ_TYPE_VFSMOUNT); } /* @@ -72,14 +47,6 @@ struct fsnotify_mark *fsnotify_find_vfsmount_mark(struct fsnotify_group *group, struct vfsmount *mnt) { struct mount *m = real_mount(mnt); - struct fsnotify_mark *mark; - - if (!m->mnt_fsnotify_marks) - return NULL; - - spin_lock(&mnt->mnt_root->d_lock); - mark = fsnotify_find_mark(&m->mnt_fsnotify_marks->list, group); - spin_unlock(&mnt->mnt_root->d_lock); - return mark; + return fsnotify_find_mark(&m->mnt_fsnotify_marks, group); } diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 01df00327683..f3df68a64ee6 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -200,6 +200,7 @@ struct fsnotify_group { * to it. The structure is protected by fsnotify_mark_srcu. */ struct fsnotify_mark_connector { + spinlock_t lock; #define FSNOTIFY_OBJ_TYPE_INODE 0x01 #define FSNOTIFY_OBJ_TYPE_VFSMOUNT 0x02 unsigned int flags; /* Type of object [lock] */ @@ -243,7 +244,7 @@ struct fsnotify_mark { struct list_head g_list; /* Protects inode / mnt pointers, flags, masks */ spinlock_t lock; - /* List of marks for inode / vfsmount [obj_lock] */ + /* List of marks for inode / vfsmount [connector->lock] */ struct hlist_node obj_list; /* Head of list of marks for an object [mark->lock, group->mark_mutex] */ struct fsnotify_mark_connector *connector; @@ -330,6 +331,8 @@ extern struct fsnotify_event *fsnotify_remove_first_event(struct fsnotify_group /* functions used to manipulate the marks attached to inodes */ +/* Calculate mask of events for a list of marks */ +extern void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn); /* run all marks associated with a vfsmount and update mnt->mnt_fsnotify_mask */ extern void fsnotify_recalc_vfsmount_mask(struct vfsmount *mnt); /* run all marks associated with an inode and update inode->i_fsnotify_mask */ -- 2.10.2