fsnotify: send event with parent/name info to sb/mount/non-dir marks
authorAmir Goldstein <amir73il@gmail.com>
Thu, 16 Jul 2020 08:42:23 +0000 (11:42 +0300)
committerJan Kara <jack@suse.cz>
Mon, 27 Jul 2020 21:21:02 +0000 (23:21 +0200)
Similar to events "on child" to watching directory, send event
with parent/name info if sb/mount/non-dir marks are interested in
parent/name info.

The FS_EVENT_ON_CHILD flag can be set on sb/mount/non-dir marks to specify
interest in parent/name info for events on non-directory inodes.

Events on "orphan" children (disconnected dentries) are sent without
parent/name info.

Events on directories are sent with parent/name info only if the parent
directory is watching.

After this change, even groups that do not subscribe to events on
children could get an event with mark iterator type TYPE_CHILD and
without mark iterator type TYPE_INODE if fanotify has marks on the same
objects.

dnotify and inotify event handlers can already cope with that situation.
audit does not subscribe to events that are possible on child, so won't
get to this situation. nfsd does not access the marks iterator from its
event handler at the moment, so it is not affected.

This is a bit too fragile, so we should prepare all groups to cope with
mark type TYPE_CHILD preferably using a generic helper.

Link: https://lore.kernel.org/r/20200716084230.30611-16-amir73il@gmail.com
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Jan Kara <jack@suse.cz>
fs/notify/fsnotify.c
include/linux/fsnotify.h
include/linux/fsnotify_backend.h

index 4a762c8..494d5d7 100644 (file)
@@ -142,38 +142,81 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
        spin_unlock(&inode->i_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)
+{
+       __u32 marks_mask = 0;
+
+       /* We only send parent/name to inode/sb/mount for events on non-dir */
+       if (mask & FS_ISDIR)
+               return false;
+
+       /* Did either inode/sb/mount subscribe for events with parent/name? */
+       marks_mask |= fsnotify_parent_needed_mask(inode->i_fsnotify_mask);
+       marks_mask |= fsnotify_parent_needed_mask(inode->i_sb->s_fsnotify_mask);
+       if (mnt)
+               marks_mask |= fsnotify_parent_needed_mask(mnt->mnt_fsnotify_mask);
+
+       /* Did they subscribe for this event with parent/name info? */
+       return mask & marks_mask;
+}
+
 /*
  * Notify this dentry's parent about a child's events with child name info
- * if parent is watching.
- * Notify only the child without name info if parent is not watching.
+ * if parent is watching or if inode/sb/mount are interested in events with
+ * parent and name info.
+ *
+ * Notify only the child without name info if parent is not watching and
+ * inode/sb/mount are not interested in events with parent and name info.
  */
 int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
                      int data_type)
 {
+       const struct path *path = fsnotify_data_path(data, data_type);
+       struct mount *mnt = path ? real_mount(path->mnt) : NULL;
        struct inode *inode = d_inode(dentry);
        struct dentry *parent;
+       bool parent_watched = dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED;
+       __u32 p_mask;
        struct inode *p_inode = NULL;
        struct name_snapshot name;
        struct qstr *file_name = NULL;
        int ret = 0;
 
+       /*
+        * Do inode/sb/mount care about parent and name info on non-dir?
+        * Do they care about any event at all?
+        */
+       if (!inode->i_fsnotify_marks && !inode->i_sb->s_fsnotify_marks &&
+           (!mnt || !mnt->mnt_fsnotify_marks) && !parent_watched)
+               return 0;
+
        parent = NULL;
-       if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED))
+       if (!parent_watched && !fsnotify_event_needs_parent(inode, mnt, mask))
                goto notify;
 
+       /* Does parent inode care about events on children? */
        parent = dget_parent(dentry);
        p_inode = parent->d_inode;
-
-       if (unlikely(!fsnotify_inode_watches_children(p_inode))) {
+       p_mask = fsnotify_inode_watches_children(p_inode);
+       if (unlikely(parent_watched && !p_mask))
                __fsnotify_update_child_dentry_flags(p_inode);
-       } else if (p_inode->i_fsnotify_mask & mask & ALL_FSNOTIFY_EVENTS) {
+
+       /*
+        * Include parent/name in notification either if some notification
+        * groups require parent info (!parent_watched case) or the parent is
+        * interested in this event.
+        */
+       if (!parent_watched || (mask & p_mask & ALL_FSNOTIFY_EVENTS)) {
                /* When notifying parent, child should be passed as data */
                WARN_ON_ONCE(inode != fsnotify_data_inode(data, data_type));
 
                /* Notify both parent and child with child name info */
                take_dentry_name_snapshot(&name, dentry);
                file_name = &name.name;
-               mask |= FS_EVENT_ON_CHILD;
+               if (parent_watched)
+                       mask |= FS_EVENT_ON_CHILD;
        }
 
 notify:
@@ -349,8 +392,8 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
                inode = dir;
        } else if (mask & FS_EVENT_ON_CHILD) {
                /*
-                * Event on child - report on TYPE_INODE to dir
-                * and on TYPE_CHILD to child.
+                * Event on child - report on TYPE_INODE to dir if it is
+                * watching children and on TYPE_CHILD to child.
                 */
                child = inode;
                inode = dir;
@@ -364,14 +407,17 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
         * SRCU because we have no references to any objects and do not
         * need SRCU to keep them "alive".
         */
-       if (!inode->i_fsnotify_marks && !sb->s_fsnotify_marks &&
+       if (!sb->s_fsnotify_marks &&
            (!mnt || !mnt->mnt_fsnotify_marks) &&
+           (!inode || !inode->i_fsnotify_marks) &&
            (!child || !child->i_fsnotify_marks))
                return 0;
 
-       marks_mask = inode->i_fsnotify_mask | sb->s_fsnotify_mask;
+       marks_mask = sb->s_fsnotify_mask;
        if (mnt)
                marks_mask |= mnt->mnt_fsnotify_mask;
+       if (inode)
+               marks_mask |= inode->i_fsnotify_mask;
        if (child)
                marks_mask |= child->i_fsnotify_mask;
 
@@ -386,14 +432,16 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
 
        iter_info.srcu_idx = srcu_read_lock(&fsnotify_mark_srcu);
 
-       iter_info.marks[FSNOTIFY_OBJ_TYPE_INODE] =
-               fsnotify_first_mark(&inode->i_fsnotify_marks);
        iter_info.marks[FSNOTIFY_OBJ_TYPE_SB] =
                fsnotify_first_mark(&sb->s_fsnotify_marks);
        if (mnt) {
                iter_info.marks[FSNOTIFY_OBJ_TYPE_VFSMOUNT] =
                        fsnotify_first_mark(&mnt->mnt_fsnotify_marks);
        }
+       if (inode) {
+               iter_info.marks[FSNOTIFY_OBJ_TYPE_INODE] =
+                       fsnotify_first_mark(&inode->i_fsnotify_marks);
+       }
        if (child) {
                iter_info.marks[FSNOTIFY_OBJ_TYPE_CHILD] =
                        fsnotify_first_mark(&child->i_fsnotify_marks);
index 99922ca..6e63f7e 100644 (file)
@@ -53,10 +53,16 @@ static inline int fsnotify_parent(struct dentry *dentry, __u32 mask,
 {
        struct inode *inode = d_inode(dentry);
 
-       if (S_ISDIR(inode->i_mode))
+       if (S_ISDIR(inode->i_mode)) {
                mask |= FS_ISDIR;
 
-       if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED))
+               /* sb/mount marks are not interested in name of directory */
+               if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED))
+                       goto notify_child;
+       }
+
+       /* disconnected dentry cannot notify parent */
+       if (IS_ROOT(dentry))
                goto notify_child;
 
        return __fsnotify_parent(dentry, mask, data, data_type);
index 1525206..32104cf 100644 (file)
 #define FS_OPEN_EXEC_PERM      0x00040000      /* open/exec event in a permission hook */
 
 #define FS_EXCL_UNLINK         0x04000000      /* do not send events if object is unlinked */
-/* This inode cares about things that happen to its children.  Always set for
- * dnotify and inotify. */
+/*
+ * Set on inode mark that cares about things that happen to its children.
+ * Always set for dnotify and inotify.
+ * Set on inode/sb/mount marks that care about parent/name info.
+ */
 #define FS_EVENT_ON_CHILD      0x08000000
 
 #define FS_DN_RENAME           0x10000000      /* file renamed */
                                  FS_OPEN_EXEC_PERM)
 
 /*
- * This is a list of all events that may get sent to a parent based on fs event
- * happening to inodes inside that directory.
+ * This is a list of all events that may get sent to a parent that is watching
+ * with flag FS_EVENT_ON_CHILD based on fs event on a child of that directory.
  */
 #define FS_EVENTS_POSS_ON_CHILD   (ALL_FSNOTIFY_PERM_EVENTS | \
                                   FS_ACCESS | FS_MODIFY | FS_ATTRIB | \
                                   FS_CLOSE_WRITE | FS_CLOSE_NOWRITE | \
                                   FS_OPEN | FS_OPEN_EXEC)
 
+/*
+ * This is a list of all events that may get sent with the parent inode as the
+ * @to_tell argument of fsnotify().
+ * It may include events that can be sent to an inode/sb/mount mark, but cannot
+ * be sent to a parent watching children.
+ */
+#define FS_EVENTS_POSS_TO_PARENT (FS_EVENTS_POSS_ON_CHILD)
+
 /* Events that can be reported to backends */
 #define ALL_FSNOTIFY_EVENTS (ALL_FSNOTIFY_DIRENT_EVENTS | \
                             FS_EVENTS_POSS_ON_CHILD | \
@@ -397,6 +408,19 @@ extern void __fsnotify_vfsmount_delete(struct vfsmount *mnt);
 extern void fsnotify_sb_delete(struct super_block *sb);
 extern u32 fsnotify_get_cookie(void);
 
+static inline __u32 fsnotify_parent_needed_mask(__u32 mask)
+{
+       /* FS_EVENT_ON_CHILD is set on marks that want parent/name info */
+       if (!(mask & FS_EVENT_ON_CHILD))
+               return 0;
+       /*
+        * This object might be watched by a mark that cares about parent/name
+        * info, does it care about the specific set of events that can be
+        * reported with parent/name info?
+        */
+       return mask & FS_EVENTS_POSS_TO_PARENT;
+}
+
 static inline int fsnotify_inode_watches_children(struct inode *inode)
 {
        /* FS_EVENT_ON_CHILD is set if the inode may care */