Commit 43b245a7 authored by Amir Goldstein's avatar Amir Goldstein Committed by Jan Kara

fsnotify: create helpers for group mark_mutex lock

Create helpers to take and release the group mark_mutex lock.

Define a flag FSNOTIFY_GROUP_NOFS in fsnotify_group that determines
if the mark_mutex lock is fs reclaim safe or not.  If not safe, the
lock helpers take the lock and disable direct fs reclaim.

In that case we annotate the mutex with a different lockdep class to
express to lockdep that an allocation of mark of an fs reclaim safe group
may take the group lock of another "NOFS" group to evict inodes.

For now, converted only the callers in common code and no backend
defines the NOFS flag.  It is intended to be set by fanotify for
evictable marks support.

Link: https://lore.kernel.org/r/20220422120327.3459282-7-amir73il@gmail.comSuggested-by: default avatarJan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20220321112310.vpr7oxro2xkz5llh@quack3.lan/Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
parent f3010343
...@@ -28,13 +28,13 @@ static void show_fdinfo(struct seq_file *m, struct file *f, ...@@ -28,13 +28,13 @@ static void show_fdinfo(struct seq_file *m, struct file *f,
struct fsnotify_group *group = f->private_data; struct fsnotify_group *group = f->private_data;
struct fsnotify_mark *mark; struct fsnotify_mark *mark;
mutex_lock(&group->mark_mutex); fsnotify_group_lock(group);
list_for_each_entry(mark, &group->marks_list, g_list) { list_for_each_entry(mark, &group->marks_list, g_list) {
show(m, mark); show(m, mark);
if (seq_has_overflowed(m)) if (seq_has_overflowed(m))
break; break;
} }
mutex_unlock(&group->mark_mutex); fsnotify_group_unlock(group);
} }
#if defined(CONFIG_EXPORTFS) #if defined(CONFIG_EXPORTFS)
......
...@@ -115,6 +115,7 @@ static struct fsnotify_group *__fsnotify_alloc_group( ...@@ -115,6 +115,7 @@ static struct fsnotify_group *__fsnotify_alloc_group(
const struct fsnotify_ops *ops, const struct fsnotify_ops *ops,
int flags, gfp_t gfp) int flags, gfp_t gfp)
{ {
static struct lock_class_key nofs_marks_lock;
struct fsnotify_group *group; struct fsnotify_group *group;
group = kzalloc(sizeof(struct fsnotify_group), gfp); group = kzalloc(sizeof(struct fsnotify_group), gfp);
...@@ -135,6 +136,16 @@ static struct fsnotify_group *__fsnotify_alloc_group( ...@@ -135,6 +136,16 @@ static struct fsnotify_group *__fsnotify_alloc_group(
group->ops = ops; group->ops = ops;
group->flags = flags; group->flags = flags;
/*
* For most backends, eviction of inode with a mark is not expected,
* because marks hold a refcount on the inode against eviction.
*
* Use a different lockdep class for groups that support evictable
* inode marks, because with evictable marks, mark_mutex is NOT
* fs-reclaim safe - the mutex is taken when evicting inodes.
*/
if (flags & FSNOTIFY_GROUP_NOFS)
lockdep_set_class(&group->mark_mutex, &nofs_marks_lock);
return group; return group;
} }
......
...@@ -398,9 +398,7 @@ void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info) ...@@ -398,9 +398,7 @@ void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info)
*/ */
void fsnotify_detach_mark(struct fsnotify_mark *mark) void fsnotify_detach_mark(struct fsnotify_mark *mark)
{ {
struct fsnotify_group *group = mark->group; fsnotify_group_assert_locked(mark->group);
WARN_ON_ONCE(!mutex_is_locked(&group->mark_mutex));
WARN_ON_ONCE(!srcu_read_lock_held(&fsnotify_mark_srcu) && WARN_ON_ONCE(!srcu_read_lock_held(&fsnotify_mark_srcu) &&
refcount_read(&mark->refcnt) < 1 + refcount_read(&mark->refcnt) < 1 +
!!(mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED)); !!(mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED));
...@@ -452,9 +450,9 @@ void fsnotify_free_mark(struct fsnotify_mark *mark) ...@@ -452,9 +450,9 @@ void fsnotify_free_mark(struct fsnotify_mark *mark)
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(&group->mark_mutex); fsnotify_group_lock(group);
fsnotify_detach_mark(mark); fsnotify_detach_mark(mark);
mutex_unlock(&group->mark_mutex); fsnotify_group_unlock(group);
fsnotify_free_mark(mark); fsnotify_free_mark(mark);
} }
EXPORT_SYMBOL_GPL(fsnotify_destroy_mark); EXPORT_SYMBOL_GPL(fsnotify_destroy_mark);
...@@ -673,7 +671,7 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark, ...@@ -673,7 +671,7 @@ int fsnotify_add_mark_locked(struct fsnotify_mark *mark,
struct fsnotify_group *group = mark->group; struct fsnotify_group *group = mark->group;
int ret = 0; int ret = 0;
BUG_ON(!mutex_is_locked(&group->mark_mutex)); fsnotify_group_assert_locked(group);
/* /*
* LOCKING ORDER!!!! * LOCKING ORDER!!!!
...@@ -714,9 +712,9 @@ int fsnotify_add_mark(struct fsnotify_mark *mark, fsnotify_connp_t *connp, ...@@ -714,9 +712,9 @@ int fsnotify_add_mark(struct fsnotify_mark *mark, fsnotify_connp_t *connp,
int ret; int ret;
struct fsnotify_group *group = mark->group; struct fsnotify_group *group = mark->group;
mutex_lock(&group->mark_mutex); fsnotify_group_lock(group);
ret = fsnotify_add_mark_locked(mark, connp, obj_type, add_flags, fsid); ret = fsnotify_add_mark_locked(mark, connp, obj_type, add_flags, fsid);
mutex_unlock(&group->mark_mutex); fsnotify_group_unlock(group);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(fsnotify_add_mark); EXPORT_SYMBOL_GPL(fsnotify_add_mark);
...@@ -770,24 +768,24 @@ void fsnotify_clear_marks_by_group(struct fsnotify_group *group, ...@@ -770,24 +768,24 @@ void fsnotify_clear_marks_by_group(struct fsnotify_group *group,
* move marks to free to to_free list in one go and then free marks in * move marks to free to to_free list in one go and then free marks in
* to_free list one by one. * to_free list one by one.
*/ */
mutex_lock(&group->mark_mutex); fsnotify_group_lock(group);
list_for_each_entry_safe(mark, lmark, &group->marks_list, g_list) { list_for_each_entry_safe(mark, lmark, &group->marks_list, g_list) {
if (mark->connector->type == obj_type) if (mark->connector->type == obj_type)
list_move(&mark->g_list, &to_free); list_move(&mark->g_list, &to_free);
} }
mutex_unlock(&group->mark_mutex); fsnotify_group_unlock(group);
clear: clear:
while (1) { while (1) {
mutex_lock(&group->mark_mutex); fsnotify_group_lock(group);
if (list_empty(head)) { if (list_empty(head)) {
mutex_unlock(&group->mark_mutex); fsnotify_group_unlock(group);
break; break;
} }
mark = list_first_entry(head, struct fsnotify_mark, g_list); mark = list_first_entry(head, struct fsnotify_mark, g_list);
fsnotify_get_mark(mark); fsnotify_get_mark(mark);
fsnotify_detach_mark(mark); fsnotify_detach_mark(mark);
mutex_unlock(&group->mark_mutex); fsnotify_group_unlock(group);
fsnotify_free_mark(mark); fsnotify_free_mark(mark);
fsnotify_put_mark(mark); fsnotify_put_mark(mark);
} }
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/user_namespace.h> #include <linux/user_namespace.h>
#include <linux/refcount.h> #include <linux/refcount.h>
#include <linux/mempool.h> #include <linux/mempool.h>
#include <linux/sched/mm.h>
/* /*
* IN_* from inotfy.h lines up EXACTLY with FS_*, this is so we can easily * IN_* from inotfy.h lines up EXACTLY with FS_*, this is so we can easily
...@@ -212,7 +213,9 @@ struct fsnotify_group { ...@@ -212,7 +213,9 @@ struct fsnotify_group {
#define FSNOTIFY_GROUP_USER 0x01 /* user allocated group */ #define FSNOTIFY_GROUP_USER 0x01 /* user allocated group */
#define FSNOTIFY_GROUP_DUPS 0x02 /* allow multiple marks per object */ #define FSNOTIFY_GROUP_DUPS 0x02 /* allow multiple marks per object */
#define FSNOTIFY_GROUP_NOFS 0x04 /* group lock is not direct reclaim safe */
int flags; int flags;
unsigned int owner_flags; /* stored flags of mark_mutex owner */
/* stores all fastpath marks assoc with this group so they can be cleaned on unregister */ /* stores all fastpath marks assoc with this group so they can be cleaned on unregister */
struct mutex mark_mutex; /* protect marks_list */ struct mutex mark_mutex; /* protect marks_list */
...@@ -254,6 +257,31 @@ struct fsnotify_group { ...@@ -254,6 +257,31 @@ struct fsnotify_group {
}; };
}; };
/*
* These helpers are used to prevent deadlock when reclaiming inodes with
* evictable marks of the same group that is allocating a new mark.
*/
static inline void fsnotify_group_lock(struct fsnotify_group *group)
{
mutex_lock(&group->mark_mutex);
if (group->flags & FSNOTIFY_GROUP_NOFS)
group->owner_flags = memalloc_nofs_save();
}
static inline void fsnotify_group_unlock(struct fsnotify_group *group)
{
if (group->flags & FSNOTIFY_GROUP_NOFS)
memalloc_nofs_restore(group->owner_flags);
mutex_unlock(&group->mark_mutex);
}
static inline void fsnotify_group_assert_locked(struct fsnotify_group *group)
{
WARN_ON_ONCE(!mutex_is_locked(&group->mark_mutex));
if (group->flags & FSNOTIFY_GROUP_NOFS)
WARN_ON_ONCE(!(current->flags & PF_MEMALLOC_NOFS));
}
/* When calling fsnotify tell it if the data is a path or inode */ /* When calling fsnotify tell it if the data is a path or inode */
enum fsnotify_data_type { enum fsnotify_data_type {
FSNOTIFY_EVENT_NONE, FSNOTIFY_EVENT_NONE,
......
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