Skip to content

Commit d601832

Browse files
committed
Merge tag 'fsnotify_for_v6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs
Pull fsnotify fix from Jan Kara: "Fix possible softlockups on directories with many dentries in fsnotify code" * tag 'fsnotify_for_v6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs: fsnotify: clear PARENT_WATCHED flags lazily
2 parents bf3aa9d + 172e422 commit d601832

File tree

4 files changed

+56
-17
lines changed

4 files changed

+56
-17
lines changed

fs/notify/fsnotify.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,13 @@ void fsnotify_sb_free(struct super_block *sb)
117117
* parent cares. Thus when an event happens on a child it can quickly tell
118118
* if there is a need to find a parent and send the event to the parent.
119119
*/
120-
void __fsnotify_update_child_dentry_flags(struct inode *inode)
120+
void fsnotify_set_children_dentry_flags(struct inode *inode)
121121
{
122122
struct dentry *alias;
123-
int watched;
124123

125124
if (!S_ISDIR(inode->i_mode))
126125
return;
127126

128-
/* determine if the children should tell inode about their events */
129-
watched = fsnotify_inode_watches_children(inode);
130-
131127
spin_lock(&inode->i_lock);
132128
/* run all of the dentries associated with this inode. Since this is a
133129
* directory, there damn well better only be one item on this list */
@@ -143,17 +139,32 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
143139
continue;
144140

145141
spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
146-
if (watched)
147-
child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;
148-
else
149-
child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
142+
child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;
150143
spin_unlock(&child->d_lock);
151144
}
152145
spin_unlock(&alias->d_lock);
153146
}
154147
spin_unlock(&inode->i_lock);
155148
}
156149

150+
/*
151+
* Lazily clear false positive PARENT_WATCHED flag for child whose parent had
152+
* stopped watching children.
153+
*/
154+
static void fsnotify_clear_child_dentry_flag(struct inode *pinode,
155+
struct dentry *dentry)
156+
{
157+
spin_lock(&dentry->d_lock);
158+
/*
159+
* d_lock is a sufficient barrier to prevent observing a non-watched
160+
* parent state from before the fsnotify_set_children_dentry_flags()
161+
* or fsnotify_update_flags() call that had set PARENT_WATCHED.
162+
*/
163+
if (!fsnotify_inode_watches_children(pinode))
164+
dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
165+
spin_unlock(&dentry->d_lock);
166+
}
167+
157168
/* Are inode/sb/mount interested in parent and name info with this event? */
158169
static bool fsnotify_event_needs_parent(struct inode *inode, __u32 mnt_mask,
159170
__u32 mask)
@@ -228,7 +239,7 @@ int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
228239
p_inode = parent->d_inode;
229240
p_mask = fsnotify_inode_watches_children(p_inode);
230241
if (unlikely(parent_watched && !p_mask))
231-
__fsnotify_update_child_dentry_flags(p_inode);
242+
fsnotify_clear_child_dentry_flag(p_inode, dentry);
232243

233244
/*
234245
* Include parent/name in notification either if some notification

fs/notify/fsnotify.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ static inline void fsnotify_clear_marks_by_sb(struct super_block *sb)
9393
* update the dentry->d_flags of all of inode's children to indicate if inode cares
9494
* about events that happen to its children.
9595
*/
96-
extern void __fsnotify_update_child_dentry_flags(struct inode *inode);
96+
extern void fsnotify_set_children_dentry_flags(struct inode *inode);
9797

9898
extern struct kmem_cache *fsnotify_mark_connector_cachep;
9999

fs/notify/mark.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,24 @@ static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
250250
return fsnotify_update_iref(conn, want_iref);
251251
}
252252

253+
static bool fsnotify_conn_watches_children(
254+
struct fsnotify_mark_connector *conn)
255+
{
256+
if (conn->type != FSNOTIFY_OBJ_TYPE_INODE)
257+
return false;
258+
259+
return fsnotify_inode_watches_children(fsnotify_conn_inode(conn));
260+
}
261+
262+
static void fsnotify_conn_set_children_dentry_flags(
263+
struct fsnotify_mark_connector *conn)
264+
{
265+
if (conn->type != FSNOTIFY_OBJ_TYPE_INODE)
266+
return;
267+
268+
fsnotify_set_children_dentry_flags(fsnotify_conn_inode(conn));
269+
}
270+
253271
/*
254272
* Calculate mask of events for a list of marks. The caller must make sure
255273
* connector and connector->obj cannot disappear under us. Callers achieve
@@ -258,15 +276,23 @@ static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
258276
*/
259277
void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
260278
{
279+
bool update_children;
280+
261281
if (!conn)
262282
return;
263283

264284
spin_lock(&conn->lock);
285+
update_children = !fsnotify_conn_watches_children(conn);
265286
__fsnotify_recalc_mask(conn);
287+
update_children &= fsnotify_conn_watches_children(conn);
266288
spin_unlock(&conn->lock);
267-
if (conn->type == FSNOTIFY_OBJ_TYPE_INODE)
268-
__fsnotify_update_child_dentry_flags(
269-
fsnotify_conn_inode(conn));
289+
/*
290+
* Set children's PARENT_WATCHED flags only if parent started watching.
291+
* When parent stops watching, we clear false positive PARENT_WATCHED
292+
* flags lazily in __fsnotify_parent().
293+
*/
294+
if (update_children)
295+
fsnotify_conn_set_children_dentry_flags(conn);
270296
}
271297

272298
/* Free all connectors queued for freeing once SRCU period ends */

include/linux/fsnotify_backend.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -594,12 +594,14 @@ static inline __u32 fsnotify_parent_needed_mask(__u32 mask)
594594

595595
static inline int fsnotify_inode_watches_children(struct inode *inode)
596596
{
597+
__u32 parent_mask = READ_ONCE(inode->i_fsnotify_mask);
598+
597599
/* FS_EVENT_ON_CHILD is set if the inode may care */
598-
if (!(inode->i_fsnotify_mask & FS_EVENT_ON_CHILD))
600+
if (!(parent_mask & FS_EVENT_ON_CHILD))
599601
return 0;
600602
/* this inode might care about child events, does it care about the
601603
* specific set of events that can happen on a child? */
602-
return inode->i_fsnotify_mask & FS_EVENTS_POSS_ON_CHILD;
604+
return parent_mask & FS_EVENTS_POSS_ON_CHILD;
603605
}
604606

605607
/*
@@ -613,7 +615,7 @@ static inline void fsnotify_update_flags(struct dentry *dentry)
613615
/*
614616
* Serialisation of setting PARENT_WATCHED on the dentries is provided
615617
* by d_lock. If inotify_inode_watched changes after we have taken
616-
* d_lock, the following __fsnotify_update_child_dentry_flags call will
618+
* d_lock, the following fsnotify_set_children_dentry_flags call will
617619
* find our entry, so it will spin until we complete here, and update
618620
* us with the new state.
619621
*/

0 commit comments

Comments
 (0)