[PATCH 08/25] fsnotify: Lock object list with connector lock

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux