diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index b5d8f238f..7974e91ff 100755 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -103,13 +103,17 @@ void fsnotify_sb_delete(struct super_block *sb) * parent cares. Thus when an event happens on a child it can quickly tell * if there is a need to find a parent and send the event to the parent. */ -void fsnotify_set_children_dentry_flags(struct inode *inode) +void __fsnotify_update_child_dentry_flags(struct inode *inode) { struct dentry *alias; + int watched; if (!S_ISDIR(inode->i_mode)) return; + /* determine if the children should tell inode about their events */ + watched = fsnotify_inode_watches_children(inode); + spin_lock(&inode->i_lock); /* run all of the dentries associated with this inode. Since this is a * directory, there damn well better only be one item on this list */ @@ -125,7 +129,10 @@ void fsnotify_set_children_dentry_flags(struct inode *inode) continue; spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED); - child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED; + if (watched) + child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED; + else + child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED; spin_unlock(&child->d_lock); } spin_unlock(&alias->d_lock); @@ -133,24 +140,6 @@ void fsnotify_set_children_dentry_flags(struct inode *inode) spin_unlock(&inode->i_lock); } -/* - * Lazily clear false positive PARENT_WATCHED flag for child whose parent had - * stopped watching children. - */ -static void fsnotify_clear_child_dentry_flag(struct inode *pinode, - struct dentry *dentry) -{ - spin_lock(&dentry->d_lock); - /* - * d_lock is a sufficient barrier to prevent observing a non-watched - * parent state from before the fsnotify_set_children_dentry_flags() - * or fsnotify_update_flags() call that had set PARENT_WATCHED. - */ - if (!fsnotify_inode_watches_children(pinode)) - dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED; - spin_unlock(&dentry->d_lock); -} - /* Are inode/sb/mount interested in parent and name info with this event? */ static bool fsnotify_event_needs_parent(struct inode *inode, struct mount *mnt, __u32 mask) @@ -219,7 +208,7 @@ int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data, p_inode = parent->d_inode; p_mask = fsnotify_inode_watches_children(p_inode); if (unlikely(parent_watched && !p_mask)) - fsnotify_clear_child_dentry_flag(p_inode, dentry); + __fsnotify_update_child_dentry_flags(p_inode); /* * Include parent/name in notification either if some notification diff --git a/fs/notify/fsnotify.h b/fs/notify/fsnotify.h index 2b4267de8..fde74eb33 100755 --- a/fs/notify/fsnotify.h +++ b/fs/notify/fsnotify.h @@ -74,7 +74,7 @@ static inline void fsnotify_clear_marks_by_sb(struct super_block *sb) * update the dentry->d_flags of all of inode's children to indicate if inode cares * about events that happen to its children. */ -extern void fsnotify_set_children_dentry_flags(struct inode *inode); +extern void __fsnotify_update_child_dentry_flags(struct inode *inode); extern struct kmem_cache *fsnotify_mark_connector_cachep; diff --git a/fs/notify/mark.c b/fs/notify/mark.c index 4be6e883d..c74ef9474 100755 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -176,24 +176,6 @@ static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) return fsnotify_update_iref(conn, want_iref); } -static bool fsnotify_conn_watches_children( - struct fsnotify_mark_connector *conn) -{ - if (conn->type != FSNOTIFY_OBJ_TYPE_INODE) - return false; - - return fsnotify_inode_watches_children(fsnotify_conn_inode(conn)); -} - -static void fsnotify_conn_set_children_dentry_flags( - struct fsnotify_mark_connector *conn) -{ - if (conn->type != FSNOTIFY_OBJ_TYPE_INODE) - return; - - fsnotify_set_children_dentry_flags(fsnotify_conn_inode(conn)); -} - /* * Calculate mask of events for a list of marks. The caller must make sure * connector and connector->obj cannot disappear under us. Callers achieve @@ -202,23 +184,15 @@ static void fsnotify_conn_set_children_dentry_flags( */ void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) { - bool update_children; - if (!conn) return; spin_lock(&conn->lock); - update_children = !fsnotify_conn_watches_children(conn); __fsnotify_recalc_mask(conn); - update_children &= fsnotify_conn_watches_children(conn); spin_unlock(&conn->lock); - /* - * Set children's PARENT_WATCHED flags only if parent started watching. - * When parent stops watching, we clear false positive PARENT_WATCHED - * flags lazily in __fsnotify_parent(). - */ - if (update_children) - fsnotify_conn_set_children_dentry_flags(conn); + if (conn->type == FSNOTIFY_OBJ_TYPE_INODE) + __fsnotify_update_child_dentry_flags( + fsnotify_conn_inode(conn)); } /* Free all connectors queued for freeing once SRCU period ends */ diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 096b79e43..d7d96c806 100755 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -563,14 +563,12 @@ static inline __u32 fsnotify_parent_needed_mask(__u32 mask) static inline int fsnotify_inode_watches_children(struct inode *inode) { - __u32 parent_mask = READ_ONCE(inode->i_fsnotify_mask); - /* FS_EVENT_ON_CHILD is set if the inode may care */ - if (!(parent_mask & FS_EVENT_ON_CHILD)) + if (!(inode->i_fsnotify_mask & FS_EVENT_ON_CHILD)) return 0; /* this inode might care about child events, does it care about the * specific set of events that can happen on a child? */ - return parent_mask & FS_EVENTS_POSS_ON_CHILD; + return inode->i_fsnotify_mask & FS_EVENTS_POSS_ON_CHILD; } /* @@ -584,7 +582,7 @@ static inline void fsnotify_update_flags(struct dentry *dentry) /* * Serialisation of setting PARENT_WATCHED on the dentries is provided * by d_lock. If inotify_inode_watched changes after we have taken - * d_lock, the following fsnotify_set_children_dentry_flags call will + * d_lock, the following __fsnotify_update_child_dentry_flags call will * find our entry, so it will spin until we complete here, and update * us with the new state. */