Commit 9b93f331 authored by Amir Goldstein's avatar Amir Goldstein Committed by Jan Kara

fsnotify: send event with parent/name info to sb/mount/non-dir marks

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.comSigned-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
parent 7dbe6080
...@@ -142,38 +142,81 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode) ...@@ -142,38 +142,81 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
spin_unlock(&inode->i_lock); 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 * Notify this dentry's parent about a child's events with child name info
* if parent is watching. * if parent is watching or if inode/sb/mount are interested in events with
* Notify only the child without name info if parent is not watching. * 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 __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
int data_type) 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 inode *inode = d_inode(dentry);
struct dentry *parent; struct dentry *parent;
bool parent_watched = dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED;
__u32 p_mask;
struct inode *p_inode = NULL; struct inode *p_inode = NULL;
struct name_snapshot name; struct name_snapshot name;
struct qstr *file_name = NULL; struct qstr *file_name = NULL;
int ret = 0; 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; parent = NULL;
if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED)) if (!parent_watched && !fsnotify_event_needs_parent(inode, mnt, mask))
goto notify; goto notify;
/* Does parent inode care about events on children? */
parent = dget_parent(dentry); parent = dget_parent(dentry);
p_inode = parent->d_inode; p_inode = parent->d_inode;
p_mask = fsnotify_inode_watches_children(p_inode);
if (unlikely(!fsnotify_inode_watches_children(p_inode))) { if (unlikely(parent_watched && !p_mask))
__fsnotify_update_child_dentry_flags(p_inode); __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 */ /* When notifying parent, child should be passed as data */
WARN_ON_ONCE(inode != fsnotify_data_inode(data, data_type)); WARN_ON_ONCE(inode != fsnotify_data_inode(data, data_type));
/* Notify both parent and child with child name info */ /* Notify both parent and child with child name info */
take_dentry_name_snapshot(&name, dentry); take_dentry_name_snapshot(&name, dentry);
file_name = &name.name; file_name = &name.name;
mask |= FS_EVENT_ON_CHILD; if (parent_watched)
mask |= FS_EVENT_ON_CHILD;
} }
notify: notify:
...@@ -349,8 +392,8 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir, ...@@ -349,8 +392,8 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
inode = dir; inode = dir;
} else if (mask & FS_EVENT_ON_CHILD) { } else if (mask & FS_EVENT_ON_CHILD) {
/* /*
* Event on child - report on TYPE_INODE to dir * Event on child - report on TYPE_INODE to dir if it is
* and on TYPE_CHILD to child. * watching children and on TYPE_CHILD to child.
*/ */
child = inode; child = inode;
inode = dir; inode = dir;
...@@ -364,14 +407,17 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct 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 * SRCU because we have no references to any objects and do not
* need SRCU to keep them "alive". * 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) && (!mnt || !mnt->mnt_fsnotify_marks) &&
(!inode || !inode->i_fsnotify_marks) &&
(!child || !child->i_fsnotify_marks)) (!child || !child->i_fsnotify_marks))
return 0; return 0;
marks_mask = inode->i_fsnotify_mask | sb->s_fsnotify_mask; marks_mask = sb->s_fsnotify_mask;
if (mnt) if (mnt)
marks_mask |= mnt->mnt_fsnotify_mask; marks_mask |= mnt->mnt_fsnotify_mask;
if (inode)
marks_mask |= inode->i_fsnotify_mask;
if (child) if (child)
marks_mask |= child->i_fsnotify_mask; marks_mask |= child->i_fsnotify_mask;
...@@ -386,14 +432,16 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir, ...@@ -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.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] = iter_info.marks[FSNOTIFY_OBJ_TYPE_SB] =
fsnotify_first_mark(&sb->s_fsnotify_marks); fsnotify_first_mark(&sb->s_fsnotify_marks);
if (mnt) { if (mnt) {
iter_info.marks[FSNOTIFY_OBJ_TYPE_VFSMOUNT] = iter_info.marks[FSNOTIFY_OBJ_TYPE_VFSMOUNT] =
fsnotify_first_mark(&mnt->mnt_fsnotify_marks); 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) { if (child) {
iter_info.marks[FSNOTIFY_OBJ_TYPE_CHILD] = iter_info.marks[FSNOTIFY_OBJ_TYPE_CHILD] =
fsnotify_first_mark(&child->i_fsnotify_marks); fsnotify_first_mark(&child->i_fsnotify_marks);
......
...@@ -53,10 +53,16 @@ static inline int fsnotify_parent(struct dentry *dentry, __u32 mask, ...@@ -53,10 +53,16 @@ static inline int fsnotify_parent(struct dentry *dentry, __u32 mask,
{ {
struct inode *inode = d_inode(dentry); struct inode *inode = d_inode(dentry);
if (S_ISDIR(inode->i_mode)) if (S_ISDIR(inode->i_mode)) {
mask |= FS_ISDIR; 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; goto notify_child;
return __fsnotify_parent(dentry, mask, data, data_type); return __fsnotify_parent(dentry, mask, data, data_type);
......
...@@ -49,8 +49,11 @@ ...@@ -49,8 +49,11 @@
#define FS_OPEN_EXEC_PERM 0x00040000 /* open/exec event in a permission hook */ #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 */ #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_EVENT_ON_CHILD 0x08000000
#define FS_DN_RENAME 0x10000000 /* file renamed */ #define FS_DN_RENAME 0x10000000 /* file renamed */
...@@ -72,14 +75,22 @@ ...@@ -72,14 +75,22 @@
FS_OPEN_EXEC_PERM) FS_OPEN_EXEC_PERM)
/* /*
* This is a list of all events that may get sent to a parent based on fs event * This is a list of all events that may get sent to a parent that is watching
* happening to inodes inside that directory. * 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 | \ #define FS_EVENTS_POSS_ON_CHILD (ALL_FSNOTIFY_PERM_EVENTS | \
FS_ACCESS | FS_MODIFY | FS_ATTRIB | \ FS_ACCESS | FS_MODIFY | FS_ATTRIB | \
FS_CLOSE_WRITE | FS_CLOSE_NOWRITE | \ FS_CLOSE_WRITE | FS_CLOSE_NOWRITE | \
FS_OPEN | FS_OPEN_EXEC) 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 */ /* Events that can be reported to backends */
#define ALL_FSNOTIFY_EVENTS (ALL_FSNOTIFY_DIRENT_EVENTS | \ #define ALL_FSNOTIFY_EVENTS (ALL_FSNOTIFY_DIRENT_EVENTS | \
FS_EVENTS_POSS_ON_CHILD | \ FS_EVENTS_POSS_ON_CHILD | \
...@@ -397,6 +408,19 @@ extern void __fsnotify_vfsmount_delete(struct vfsmount *mnt); ...@@ -397,6 +408,19 @@ extern void __fsnotify_vfsmount_delete(struct vfsmount *mnt);
extern void fsnotify_sb_delete(struct super_block *sb); extern void fsnotify_sb_delete(struct super_block *sb);
extern u32 fsnotify_get_cookie(void); 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) static inline int fsnotify_inode_watches_children(struct inode *inode)
{ {
/* FS_EVENT_ON_CHILD is set if the inode may care */ /* FS_EVENT_ON_CHILD is set if the inode may care */
......
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