@@ -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);
}
/*
@@ -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);
@@ -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);
}
/**
@@ -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);
}
@@ -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);
}
@@ -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 */
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@suse.cz> --- 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(-)