Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+56 -17
+21 -10
fs/notify/fsnotify.c
··· 117 117 * parent cares. Thus when an event happens on a child it can quickly tell 118 118 * if there is a need to find a parent and send the event to the parent. 119 119 */ 120 - void __fsnotify_update_child_dentry_flags(struct inode *inode) 120 + void fsnotify_set_children_dentry_flags(struct inode *inode) 121 121 { 122 122 struct dentry *alias; 123 - int watched; 124 123 125 124 if (!S_ISDIR(inode->i_mode)) 126 125 return; 127 - 128 - /* determine if the children should tell inode about their events */ 129 - watched = fsnotify_inode_watches_children(inode); 130 126 131 127 spin_lock(&inode->i_lock); 132 128 /* run all of the dentries associated with this inode. Since this is a ··· 139 143 continue; 140 144 141 145 spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED); 142 - if (watched) 143 - child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED; 144 - else 145 - child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED; 146 + child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED; 146 147 spin_unlock(&child->d_lock); 147 148 } 148 149 spin_unlock(&alias->d_lock); 149 150 } 150 151 spin_unlock(&inode->i_lock); 152 + } 153 + 154 + /* 155 + * Lazily clear false positive PARENT_WATCHED flag for child whose parent had 156 + * stopped watching children. 157 + */ 158 + static void fsnotify_clear_child_dentry_flag(struct inode *pinode, 159 + struct dentry *dentry) 160 + { 161 + spin_lock(&dentry->d_lock); 162 + /* 163 + * d_lock is a sufficient barrier to prevent observing a non-watched 164 + * parent state from before the fsnotify_set_children_dentry_flags() 165 + * or fsnotify_update_flags() call that had set PARENT_WATCHED. 166 + */ 167 + if (!fsnotify_inode_watches_children(pinode)) 168 + dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED; 169 + spin_unlock(&dentry->d_lock); 151 170 } 152 171 153 172 /* Are inode/sb/mount interested in parent and name info with this event? */ ··· 239 228 p_inode = parent->d_inode; 240 229 p_mask = fsnotify_inode_watches_children(p_inode); 241 230 if (unlikely(parent_watched && !p_mask)) 242 - __fsnotify_update_child_dentry_flags(p_inode); 231 + fsnotify_clear_child_dentry_flag(p_inode, dentry); 243 232 244 233 /* 245 234 * Include parent/name in notification either if some notification
+1 -1
fs/notify/fsnotify.h
··· 93 93 * update the dentry->d_flags of all of inode's children to indicate if inode cares 94 94 * about events that happen to its children. 95 95 */ 96 - extern void __fsnotify_update_child_dentry_flags(struct inode *inode); 96 + extern void fsnotify_set_children_dentry_flags(struct inode *inode); 97 97 98 98 extern struct kmem_cache *fsnotify_mark_connector_cachep; 99 99
+29 -3
fs/notify/mark.c
··· 250 250 return fsnotify_update_iref(conn, want_iref); 251 251 } 252 252 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 + 253 271 /* 254 272 * Calculate mask of events for a list of marks. The caller must make sure 255 273 * connector and connector->obj cannot disappear under us. Callers achieve ··· 276 258 */ 277 259 void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn) 278 260 { 261 + bool update_children; 262 + 279 263 if (!conn) 280 264 return; 281 265 282 266 spin_lock(&conn->lock); 267 + update_children = !fsnotify_conn_watches_children(conn); 283 268 __fsnotify_recalc_mask(conn); 269 + update_children &= fsnotify_conn_watches_children(conn); 284 270 spin_unlock(&conn->lock); 285 - if (conn->type == FSNOTIFY_OBJ_TYPE_INODE) 286 - __fsnotify_update_child_dentry_flags( 287 - fsnotify_conn_inode(conn)); 271 + /* 272 + * Set children's PARENT_WATCHED flags only if parent started watching. 273 + * When parent stops watching, we clear false positive PARENT_WATCHED 274 + * flags lazily in __fsnotify_parent(). 275 + */ 276 + if (update_children) 277 + fsnotify_conn_set_children_dentry_flags(conn); 288 278 } 289 279 290 280 /* Free all connectors queued for freeing once SRCU period ends */
+5 -3
include/linux/fsnotify_backend.h
··· 594 594 595 595 static inline int fsnotify_inode_watches_children(struct inode *inode) 596 596 { 597 + __u32 parent_mask = READ_ONCE(inode->i_fsnotify_mask); 598 + 597 599 /* 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)) 599 601 return 0; 600 602 /* this inode might care about child events, does it care about the 601 603 * 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; 603 605 } 604 606 605 607 /* ··· 615 613 /* 616 614 * Serialisation of setting PARENT_WATCHED on the dentries is provided 617 615 * by d_lock. If inotify_inode_watched changes after we have taken 618 - * d_lock, the following __fsnotify_update_child_dentry_flags call will 616 + * d_lock, the following fsnotify_set_children_dentry_flags call will 619 617 * find our entry, so it will spin until we complete here, and update 620 618 * us with the new state. 621 619 */