summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2020-08-06 19:29:51 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2020-08-06 19:29:51 -0700
commiteb65405eb6860935d54b8ba90a5e231e07378be1 (patch)
treee3b557ee6cd4c0e7b26ea478222e6a07cf356a00 /fs
parent09e70bb4d89f727bafa6349155e08ce6ac0d8d9f (diff)
parent8aed8cebdd973e95d20743e00e35467c7b467d0d (diff)
Merge tag 'fsnotify_for_v5.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs
Pull fsnotify updates from Jan Kara: - fanotify fix for softlockups when there are many queued events - performance improvement to reduce fsnotify overhead when not used - Amir's implementation of fanotify events with names. With these you can now efficiently monitor whole filesystem, eg to mirror changes to another machine. * tag 'fsnotify_for_v5.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs: (37 commits) fanotify: compare fsid when merging name event fsnotify: create method handle_inode_event() in fsnotify_operations fanotify: report parent fid + child fid fanotify: report parent fid + name + child fid fanotify: add support for FAN_REPORT_NAME fanotify: report events with parent dir fid to sb/mount/non-dir marks fanotify: add basic support for FAN_REPORT_DIR_FID fsnotify: remove check that source dentry is positive fsnotify: send event with parent/name info to sb/mount/non-dir marks audit: do not set FS_EVENT_ON_CHILD in audit marks mask inotify: do not set FS_EVENT_ON_CHILD in non-dir mark mask fsnotify: pass dir and inode arguments to fsnotify() fsnotify: create helper fsnotify_inode() fsnotify: send event to parent and child with single callback inotify: report both events on parent and child with single callback dnotify: report both events on parent and child with single callback fanotify: no external fh buffer in fanotify_name_event fanotify: use struct fanotify_info to parcel the variable size buffer fsnotify: add object type "child" to object type iterator fanotify: use FAN_EVENT_ON_CHILD as implicit flag on sb/mount/non-dir marks ...
Diffstat (limited to 'fs')
-rw-r--r--fs/kernfs/file.c13
-rw-r--r--fs/nfsd/filecache.c10
-rw-r--r--fs/notify/dnotify/dnotify.c16
-rw-r--r--fs/notify/fanotify/fanotify.c443
-rw-r--r--fs/notify/fanotify/fanotify.h118
-rw-r--r--fs/notify/fanotify/fanotify_user.c218
-rw-r--r--fs/notify/fsnotify.c244
-rw-r--r--fs/notify/inotify/inotify.h6
-rw-r--r--fs/notify/inotify/inotify_fsnotify.c51
-rw-r--r--fs/notify/inotify/inotify_user.c18
10 files changed, 817 insertions, 320 deletions
diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index 06b342d8462b..f277d023ebcd 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -883,6 +883,7 @@ repeat:
list_for_each_entry(info, &kernfs_root(kn)->supers, node) {
struct kernfs_node *parent;
+ struct inode *p_inode = NULL;
struct inode *inode;
struct qstr name;
@@ -899,20 +900,20 @@ repeat:
name = (struct qstr)QSTR_INIT(kn->name, strlen(kn->name));
parent = kernfs_get_parent(kn);
if (parent) {
- struct inode *p_inode;
-
p_inode = ilookup(info->sb, kernfs_ino(parent));
if (p_inode) {
- fsnotify(p_inode, FS_MODIFY | FS_EVENT_ON_CHILD,
- inode, FSNOTIFY_EVENT_INODE, &name, 0);
+ fsnotify(FS_MODIFY | FS_EVENT_ON_CHILD,
+ inode, FSNOTIFY_EVENT_INODE,
+ p_inode, &name, inode, 0);
iput(p_inode);
}
kernfs_put(parent);
}
- fsnotify(inode, FS_MODIFY, inode, FSNOTIFY_EVENT_INODE,
- &name, 0);
+ if (!p_inode)
+ fsnotify_inode(inode, FS_MODIFY);
+
iput(inode);
}
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 82198d747c4c..c8b9d2667ee6 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -598,11 +598,9 @@ static struct notifier_block nfsd_file_lease_notifier = {
};
static int
-nfsd_file_fsnotify_handle_event(struct fsnotify_group *group,
- struct inode *inode,
- u32 mask, const void *data, int data_type,
- const struct qstr *file_name, u32 cookie,
- struct fsnotify_iter_info *iter_info)
+nfsd_file_fsnotify_handle_event(struct fsnotify_mark *mark, u32 mask,
+ struct inode *inode, struct inode *dir,
+ const struct qstr *name)
{
trace_nfsd_file_fsnotify_handle_event(inode, mask);
@@ -624,7 +622,7 @@ nfsd_file_fsnotify_handle_event(struct fsnotify_group *group,
static const struct fsnotify_ops nfsd_file_fsnotify_ops = {
- .handle_event = nfsd_file_fsnotify_handle_event,
+ .handle_inode_event = nfsd_file_fsnotify_handle_event,
.free_mark = nfsd_file_mark_free,
};
diff --git a/fs/notify/dnotify/dnotify.c b/fs/notify/dnotify/dnotify.c
index 7a42c2ebe28d..5dcda8f20c04 100644
--- a/fs/notify/dnotify/dnotify.c
+++ b/fs/notify/dnotify/dnotify.c
@@ -70,13 +70,10 @@ static void dnotify_recalc_inode_mask(struct fsnotify_mark *fsn_mark)
* destroy the dnotify struct if it was not registered to receive multiple
* events.
*/
-static int dnotify_handle_event(struct fsnotify_group *group,
- struct inode *inode,
- u32 mask, const void *data, int data_type,
- const struct qstr *file_name, u32 cookie,
- struct fsnotify_iter_info *iter_info)
+static int dnotify_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
+ struct inode *inode, struct inode *dir,
+ const struct qstr *name)
{
- struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
struct dnotify_mark *dn_mark;
struct dnotify_struct *dn;
struct dnotify_struct **prev;
@@ -84,10 +81,7 @@ static int dnotify_handle_event(struct fsnotify_group *group,
__u32 test_mask = mask & ~FS_EVENT_ON_CHILD;
/* not a dir, dnotify doesn't care */
- if (!S_ISDIR(inode->i_mode))
- return 0;
-
- if (WARN_ON(fsnotify_iter_vfsmount_mark(iter_info)))
+ if (!dir && !(mask & FS_ISDIR))
return 0;
dn_mark = container_of(inode_mark, struct dnotify_mark, fsn_mark);
@@ -127,7 +121,7 @@ static void dnotify_free_mark(struct fsnotify_mark *fsn_mark)
}
static const struct fsnotify_ops dnotify_fsnotify_ops = {
- .handle_event = dnotify_handle_event,
+ .handle_inode_event = dnotify_handle_event,
.free_mark = dnotify_free_mark,
};
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 85eda539b35f..c942910a8649 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -34,10 +34,6 @@ static bool fanotify_fh_equal(struct fanotify_fh *fh1,
if (fh1->type != fh2->type || fh1->len != fh2->len)
return false;
- /* Do not merge events if we failed to encode fh */
- if (fh1->type == FILEID_INVALID)
- return false;
-
return !fh1->len ||
!memcmp(fanotify_fh_buf(fh1), fanotify_fh_buf(fh2), fh1->len);
}
@@ -53,25 +49,47 @@ static bool fanotify_fid_event_equal(struct fanotify_fid_event *ffe1,
fanotify_fh_equal(&ffe1->object_fh, &ffe2->object_fh);
}
+static bool fanotify_info_equal(struct fanotify_info *info1,
+ struct fanotify_info *info2)
+{
+ if (info1->dir_fh_totlen != info2->dir_fh_totlen ||
+ info1->file_fh_totlen != info2->file_fh_totlen ||
+ info1->name_len != info2->name_len)
+ return false;
+
+ if (info1->dir_fh_totlen &&
+ !fanotify_fh_equal(fanotify_info_dir_fh(info1),
+ fanotify_info_dir_fh(info2)))
+ return false;
+
+ if (info1->file_fh_totlen &&
+ !fanotify_fh_equal(fanotify_info_file_fh(info1),
+ fanotify_info_file_fh(info2)))
+ return false;
+
+ return !info1->name_len ||
+ !memcmp(fanotify_info_name(info1), fanotify_info_name(info2),
+ info1->name_len);
+}
+
static bool fanotify_name_event_equal(struct fanotify_name_event *fne1,
struct fanotify_name_event *fne2)
{
- /*
- * Do not merge name events without dir fh.
- * FAN_DIR_MODIFY does not encode object fh, so it may be empty.
- */
- if (!fne1->dir_fh.len)
+ struct fanotify_info *info1 = &fne1->info;
+ struct fanotify_info *info2 = &fne2->info;
+
+ /* Do not merge name events without dir fh */
+ if (!info1->dir_fh_totlen)
return false;
- if (fne1->name_len != fne2->name_len ||
- !fanotify_fh_equal(&fne1->dir_fh, &fne2->dir_fh))
+ if (!fanotify_fsid_equal(&fne1->fsid, &fne2->fsid))
return false;
- return !memcmp(fne1->name, fne2->name, fne1->name_len);
+ return fanotify_info_equal(info1, info2);
}
static bool fanotify_should_merge(struct fsnotify_event *old_fsn,
- struct fsnotify_event *new_fsn)
+ struct fsnotify_event *new_fsn)
{
struct fanotify_event *old, *new;
@@ -83,22 +101,22 @@ static bool fanotify_should_merge(struct fsnotify_event *old_fsn,
old->type != new->type || old->pid != new->pid)
return false;
+ /*
+ * We want to merge many dirent events in the same dir (i.e.
+ * creates/unlinks/renames), but we do not want to merge dirent
+ * events referring to subdirs with dirent events referring to
+ * non subdirs, otherwise, user won't be able to tell from a
+ * mask FAN_CREATE|FAN_DELETE|FAN_ONDIR if it describes mkdir+
+ * unlink pair or rmdir+create pair of events.
+ */
+ if ((old->mask & FS_ISDIR) != (new->mask & FS_ISDIR))
+ return false;
+
switch (old->type) {
case FANOTIFY_EVENT_TYPE_PATH:
return fanotify_path_equal(fanotify_event_path(old),
fanotify_event_path(new));
case FANOTIFY_EVENT_TYPE_FID:
- /*
- * We want to merge many dirent events in the same dir (i.e.
- * creates/unlinks/renames), but we do not want to merge dirent
- * events referring to subdirs with dirent events referring to
- * non subdirs, otherwise, user won't be able to tell from a
- * mask FAN_CREATE|FAN_DELETE|FAN_ONDIR if it describes mkdir+
- * unlink pair or rmdir+create pair of events.
- */
- if ((old->mask & FS_ISDIR) != (new->mask & FS_ISDIR))
- return false;
-
return fanotify_fid_event_equal(FANOTIFY_FE(old),
FANOTIFY_FE(new));
case FANOTIFY_EVENT_TYPE_FID_NAME:
@@ -208,24 +226,30 @@ out:
static u32 fanotify_group_event_mask(struct fsnotify_group *group,
struct fsnotify_iter_info *iter_info,
u32 event_mask, const void *data,
- int data_type)
+ int data_type, struct inode *dir)
{
__u32 marks_mask = 0, marks_ignored_mask = 0;
- __u32 test_mask, user_mask = FANOTIFY_OUTGOING_EVENTS;
+ __u32 test_mask, user_mask = FANOTIFY_OUTGOING_EVENTS |
+ FANOTIFY_EVENT_FLAGS;
const struct path *path = fsnotify_data_path(data, data_type);
+ unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
struct fsnotify_mark *mark;
int type;
pr_debug("%s: report_mask=%x mask=%x data=%p data_type=%d\n",
__func__, iter_info->report_mask, event_mask, data, data_type);
- if (!FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
+ if (!fid_mode) {
/* Do we have path to open a file descriptor? */
if (!path)
return 0;
/* Path type events are only relevant for files and dirs */
if (!d_is_reg(path->dentry) && !d_can_lookup(path->dentry))
return 0;
+ } else if (!(fid_mode & FAN_REPORT_FID)) {
+ /* Do we have a directory inode to report? */
+ if (!dir && !(event_mask & FS_ISDIR))
+ return 0;
}
fsnotify_foreach_obj_type(type) {
@@ -244,12 +268,12 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
continue;
/*
- * If the event is for a child and this mark doesn't care about
- * events on a child, don't send it!
+ * If the event is for a child and this mark is on a parent not
+ * watching children, don't send it!
*/
if (event_mask & FS_EVENT_ON_CHILD &&
- (type != FSNOTIFY_OBJ_TYPE_INODE ||
- !(mark->mask & FS_EVENT_ON_CHILD)))
+ type == FSNOTIFY_OBJ_TYPE_INODE &&
+ !(mark->mask & FS_EVENT_ON_CHILD))
continue;
marks_mask |= mark->mask;
@@ -264,67 +288,102 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
*
* For backward compatibility and consistency, do not report FAN_ONDIR
* to user in legacy fanotify mode (reporting fd) and report FAN_ONDIR
- * to user in FAN_REPORT_FID mode for all event types.
+ * to user in fid mode for all event types.
+ *
+ * We never report FAN_EVENT_ON_CHILD to user, but we do pass it in to
+ * fanotify_alloc_event() when group is reporting fid as indication
+ * that event happened on child.
*/
- if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
- /* Do not report FAN_ONDIR without any event */
- if (!(test_mask & ~FAN_ONDIR))
+ if (fid_mode) {
+ /* Do not report event flags without any event */
+ if (!(test_mask & ~FANOTIFY_EVENT_FLAGS))
return 0;
} else {
- user_mask &= ~FAN_ONDIR;
+ user_mask &= ~FANOTIFY_EVENT_FLAGS;
}
return test_mask & user_mask;
}
-static void fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
- gfp_t gfp)
+/*
+ * Check size needed to encode fanotify_fh.
+ *
+ * Return size of encoded fh without fanotify_fh header.
+ * Return 0 on failure to encode.
+ */
+static int fanotify_encode_fh_len(struct inode *inode)
{
- int dwords, type, bytes = 0;
+ int dwords = 0;
+
+ if (!inode)
+ return 0;
+
+ exportfs_encode_inode_fh(inode, NULL, &dwords, NULL);
+
+ return dwords << 2;
+}
+
+/*
+ * Encode fanotify_fh.
+ *
+ * Return total size of encoded fh including fanotify_fh header.
+ * Return 0 on failure to encode.
+ */
+static int fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
+ unsigned int fh_len, gfp_t gfp)
+{
+ int dwords, type = 0;
char *ext_buf = NULL;
void *buf = fh->buf;
int err;
+ fh->type = FILEID_ROOT;
+ fh->len = 0;
+ fh->flags = 0;
if (!inode)
- goto out;
+ return 0;
- dwords = 0;
+ /*
+ * !gpf means preallocated variable size fh, but fh_len could
+ * be zero in that case if encoding fh len failed.
+ */
err = -ENOENT;
- type = exportfs_encode_inode_fh(inode, NULL, &dwords, NULL);
- if (!dwords)
+ if (fh_len < 4 || WARN_ON_ONCE(fh_len % 4))
goto out_err;
- bytes = dwords << 2;
- if (bytes > FANOTIFY_INLINE_FH_LEN) {
- /* Treat failure to allocate fh as failure to allocate event */
+ /* No external buffer in a variable size allocated fh */
+ if (gfp && fh_len > FANOTIFY_INLINE_FH_LEN) {
+ /* Treat failure to allocate fh as failure to encode fh */
err = -ENOMEM;
- ext_buf = kmalloc(bytes, gfp);
+ ext_buf = kmalloc(fh_len, gfp);
if (!ext_buf)
goto out_err;
*fanotify_fh_ext_buf_ptr(fh) = ext_buf;
buf = ext_buf;
+ fh->flags |= FANOTIFY_FH_FLAG_EXT_BUF;
}
+ dwords = fh_len >> 2;
type = exportfs_encode_inode_fh(inode, buf, &dwords, NULL);
err = -EINVAL;
- if (!type || type == FILEID_INVALID || bytes != dwords << 2)
+ if (!type || type == FILEID_INVALID || fh_len != dwords << 2)
goto out_err;
fh->type = type;
- fh->len = bytes;
+ fh->len = fh_len;
- return;
+ return FANOTIFY_FH_HDR_LEN + fh_len;
out_err:
pr_warn_ratelimited("fanotify: failed to encode fid (type=%d, len=%d, err=%i)\n",
- type, bytes, err);
+ type, fh_len, err);
kfree(ext_buf);
*fanotify_fh_ext_buf_ptr(fh) = NULL;
-out:
/* Report the event without a file identifier on encode error */
fh->type = FILEID_INVALID;
fh->len = 0;
+ return 0;
}
/*
@@ -335,27 +394,179 @@ out:
* FS_ATTRIB reports the child inode even if reported on a watched parent.
* FS_CREATE reports the modified dir inode and not the created inode.
*/
-static struct inode *fanotify_fid_inode(struct inode *to_tell, u32 event_mask,
- const void *data, int data_type)
+static struct inode *fanotify_fid_inode(u32 event_mask, const void *data,
+ int data_type, struct inode *dir)
{
if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS)
- return to_tell;
+ return dir;
+
+ return fsnotify_data_inode(data, data_type);
+}
+
+/*
+ * The inode to use as identifier when reporting dir fid depends on the event.
+ * Report the modified directory inode on dirent modification events.
+ * Report the "victim" inode if "victim" is a directory.
+ * Report the parent inode if "victim" is not a directory and event is
+ * reported to parent.
+ * Otherwise, do not report dir fid.
+ */
+static struct inode *fanotify_dfid_inode(u32 event_mask, const void *data,
+ int data_type, struct inode *dir)
+{
+ struct inode *inode = fsnotify_data_inode(data, data_type);
+
+ if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS)
+ return dir;
+
+ if (S_ISDIR(inode->i_mode))
+ return inode;
+
+ return dir;
+}
+
+static struct fanotify_event *fanotify_alloc_path_event(const struct path *path,
+ gfp_t gfp)
+{
+ struct fanotify_path_event *pevent;
+
+ pevent = kmem_cache_alloc(fanotify_path_event_cachep, gfp);
+ if (!pevent)
+ return NULL;
+
+ pevent->fae.type = FANOTIFY_EVENT_TYPE_PATH;
+ pevent->path = *path;
+ path_get(path);
+
+ return &pevent->fae;
+}
- return (struct inode *)fsnotify_data_inode(data, data_type);
+static struct fanotify_event *fanotify_alloc_perm_event(const struct path *path,
+ gfp_t gfp)
+{
+ struct fanotify_perm_event *pevent;
+
+ pevent = kmem_cache_alloc(fanotify_perm_event_cachep, gfp);
+ if (!pevent)
+ return NULL;
+
+ pevent->fae.type = FANOTIFY_EVENT_TYPE_PATH_PERM;
+ pevent->response = 0;
+ pevent->state = FAN_EVENT_INIT;
+ pevent->path = *path;
+ path_get(path);
+
+ return &pevent->fae;
}
-struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
- struct inode *inode, u32 mask,
- const void *data, int data_type,
- const struct qstr *file_name,
- __kernel_fsid_t *fsid)
+static struct fanotify_event *fanotify_alloc_fid_event(struct inode *id,
+ __kernel_fsid_t *fsid,
+ gfp_t gfp)
+{
+ struct fanotify_fid_event *ffe;
+
+ ffe = kmem_cache_alloc(fanotify_fid_event_cachep, gfp);
+ if (!ffe)
+ return NULL;
+
+ ffe->fae.type = FANOTIFY_EVENT_TYPE_FID;
+ ffe->fsid = *fsid;
+ fanotify_encode_fh(&ffe->object_fh, id, fanotify_encode_fh_len(id),
+ gfp);
+
+ return &ffe->fae;
+}
+
+static struct fanotify_event *fanotify_alloc_name_event(struct inode *id,
+ __kernel_fsid_t *fsid,
+ const struct qstr *file_name,
+ struct inode *child,
+ gfp_t gfp)
+{
+ struct fanotify_name_event *fne;
+ struct fanotify_info *info;
+ struct fanotify_fh *dfh, *ffh;
+ unsigned int dir_fh_len = fanotify_encode_fh_len(id);
+ unsigned int child_fh_len = fanotify_encode_fh_len(child);
+ unsigned int size;
+
+ size = sizeof(*fne) + FANOTIFY_FH_HDR_LEN + dir_fh_len;
+ if (child_fh_len)
+ size += FANOTIFY_FH_HDR_LEN + child_fh_len;
+ if (file_name)
+ size += file_name->len + 1;
+ fne = kmalloc(size, gfp);
+ if (!fne)
+ return NULL;
+
+ fne->fae.type = FANOTIFY_EVENT_TYPE_FID_NAME;
+ fne->fsid = *fsid;
+ info = &fne->info;
+ fanotify_info_init(info);
+ dfh = fanotify_info_dir_fh(info);
+ info->dir_fh_totlen = fanotify_encode_fh(dfh, id, dir_fh_len, 0);
+ if (child_fh_len) {
+ ffh = fanotify_info_file_fh(info);
+ info->file_fh_totlen = fanotify_encode_fh(ffh, child, child_fh_len, 0);
+ }
+ if (file_name)
+ fanotify_info_copy_name(info, file_name);
+
+ pr_debug("%s: ino=%lu size=%u dir_fh_len=%u child_fh_len=%u name_len=%u name='%.*s'\n",
+ __func__, id->i_ino, size, dir_fh_len, child_fh_len,
+ info->name_len, info->name_len, fanotify_info_name(info));
+
+ return &fne->fae;
+}
+
+static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
+ u32 mask, const void *data,
+ int data_type, struct inode *dir,
+ const struct qstr *file_name,
+ __kernel_fsid_t *fsid)
{
struct fanotify_event *event = NULL;
- struct fanotify_fid_event *ffe = NULL;
- struct fanotify_name_event *fne = NULL;
gfp_t gfp = GFP_KERNEL_ACCOUNT;
- struct inode *id = fanotify_fid_inode(inode, mask, data, data_type);
+ struct inode *id = fanotify_fid_inode(mask, data, data_type, dir);
+ struct inode *dirid = fanotify_dfid_inode(mask, data, data_type, dir);
const struct path *path = fsnotify_data_path(data, data_type);
+ unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
+ struct inode *child = NULL;
+ bool name_event = false;
+
+ if ((fid_mode & FAN_REPORT_DIR_FID) && dirid) {
+ /*
+ * With both flags FAN_REPORT_DIR_FID and FAN_REPORT_FID, we
+ * report the child fid for events reported on a non-dir child
+ * in addition to reporting the parent fid and maybe child name.
+ */
+ if ((fid_mode & FAN_REPORT_FID) &&
+ id != dirid && !(mask & FAN_ONDIR))
+ child = id;
+
+ id = dirid;
+
+ /*
+ * We record file name only in a group with FAN_REPORT_NAME
+ * and when we have a directory inode to report.
+ *
+ * For directory entry modification event, we record the fid of
+ * the directory and the name of the modified entry.
+ *
+ * For event on non-directory that is reported to parent, we
+ * record the fid of the parent and the name of the child.
+ *
+ * Even if not reporting name, we need a variable length
+ * fanotify_name_event if reporting both parent and child fids.
+ */
+ if (!(fid_mode & FAN_REPORT_NAME)) {
+ name_event = !!child;
+ file_name = NULL;
+ } else if ((mask & ALL_FSNOTIFY_DIRENT_EVENTS) ||
+ !(mask & FAN_ONDIR)) {
+ name_event = true;
+ }
+ }
/*
* For queues with unlimited length lost events are not expected and
@@ -372,87 +583,30 @@ struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
memalloc_use_memcg(group->memcg);
if (fanotify_is_perm_event(mask)) {
- struct fanotify_perm_event *pevent;
-
- pevent = kmem_cache_alloc(fanotify_perm_event_cachep, gfp);
- if (!pevent)
- goto out;
-
- event = &pevent->fae;
- event->type = FANOTIFY_EVENT_TYPE_PATH_PERM;
- pevent->response = 0;
- pevent->state = FAN_EVENT_INIT;
- goto init;
- }
-
- /*
- * For FAN_DIR_MODIFY event, we report the fid of the directory and
- * the name of the modified entry.
- * Allocate an fanotify_name_event struct and copy the name.
- */
- if (mask & FAN_DIR_MODIFY && !(WARN_ON_ONCE(!file_name))) {
- fne = kmalloc(sizeof(*fne) + file_name->len + 1, gfp);
- if (!fne)
- goto out;
-
- event = &fne->fae;
- event->type = FANOTIFY_EVENT_TYPE_FID_NAME;
- fne->name_len = file_name->len;
- strcpy(fne->name, file_name->name);
- goto init;
- }
-
- if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
- ffe = kmem_cache_alloc(fanotify_fid_event_cachep, gfp);
- if (!ffe)
- goto out;
-
- event = &ffe->fae;
- event->type = FANOTIFY_EVENT_TYPE_FID;
+ event = fanotify_alloc_perm_event(path, gfp);
+ } else if (name_event && (file_name || child)) {
+ event = fanotify_alloc_name_event(id, fsid, file_name, child,
+ gfp);
+ } else if (fid_mode) {
+ event = fanotify_alloc_fid_event(id, fsid, gfp);
} else {
- struct fanotify_path_event *pevent;
-
- pevent = kmem_cache_alloc(fanotify_path_event_cachep, gfp);
- if (!pevent)
- goto out;
-
- event = &pevent->fae;
- event->type = FANOTIFY_EVENT_TYPE_PATH;
+ event = fanotify_alloc_path_event(path, gfp);
}
-init:
+ if (!event)
+ goto out;
+
/*
* Use the victim inode instead of the watching inode as the id for
* event queue, so event reported on parent is merged with event
* reported on child when both directory and child watches exist.
*/
- fsnotify_init_event(&event->fse, (unsigned long)id);
- event->mask = mask;
+ fanotify_init_event(event, (unsigned long)id, mask);
if (FAN_GROUP_FLAG(group, FAN_REPORT_TID))
event->pid = get_pid(task_pid(current));
else
event->pid = get_pid(task_tgid(current));
- if (fsid && fanotify_event_fsid(event))
- *fanotify_event_fsid(event) = *fsid;
-
- if (fanotify_event_object_fh(event))
- fanotify_encode_fh(fanotify_event_object_fh(event), id, gfp);
-
- if (fanotify_event_dir_fh(event))
- fanotify_encode_fh(fanotify_event_dir_fh(event), id, gfp);
-
- if (fanotify_event_has_path(event)) {
- struct path *p = fanotify_event_path(event);
-
- if (path) {
- *p = *path;
- path_get(path);
- } else {
- p->mnt = NULL;
- p->dentry = NULL;
- }
- }
out:
memalloc_unuse_memcg();
return event;
@@ -491,9 +645,9 @@ static __kernel_fsid_t fanotify_get_fsid(struct fsnotify_iter_info *iter_info)
return fsid;
}
-static int fanotify_handle_event(struct fsnotify_group *group,
- struct inode *inode,
- u32 mask, const void *data, int data_type,
+static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
+ const void *data, int data_type,
+ struct inode *dir,
const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info)
{
@@ -512,7 +666,6 @@ static int fanotify_handle_event(struct fsnotify_group *group,
BUILD_BUG_ON(FAN_MOVED_FROM != FS_MOVED_FROM);
BUILD_BUG_ON(FAN_CREATE != FS_CREATE);
BUILD_BUG_ON(FAN_DELETE != FS_DELETE);
- BUILD_BUG_ON(FAN_DIR_MODIFY != FS_DIR_MODIFY);
BUILD_BUG_ON(FAN_DELETE_SELF != FS_DELETE_SELF);
BUILD_BUG_ON(FAN_MOVE_SELF != FS_MOVE_SELF);
BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD);
@@ -526,12 +679,11 @@ static int fanotify_handle_event(struct fsnotify_group *group,
BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 19);
mask = fanotify_group_event_mask(group, iter_info, mask, data,
- data_type);
+ data_type, dir);
if (!mask)
return 0;
- pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode,
- mask);
+ pr_debug("%s: group=%p mask=%x\n", __func__, group, mask);
if (fanotify_is_perm_event(mask)) {
/*
@@ -542,14 +694,14 @@ static int fanotify_handle_event(struct fsnotify_group *group,
return 0;
}
- if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
+ if (FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS)) {
fsid = fanotify_get_fsid(iter_info);
/* Racing with mark destruction or creation? */
if (!fsid.val[0] && !fsid.val[1])
return 0;
}
- event = fanotify_alloc_event(group, inode, mask, data, data_type,
+ event = fanotify_alloc_event(group, mask, data, data_type, dir,
file_name, &fsid);
ret = -ENOMEM;
if (unlikely(!event)) {
@@ -614,11 +766,7 @@ static void fanotify_free_fid_event(struct fanotify_event *event)
static void fanotify_free_name_event(struct fanotify_event *event)
{
- struct fanotify_name_event *fne = FANOTIFY_NE(event);
-
- if (fanotify_fh_has_ext_buf(&fne->dir_fh))
- kfree(fanotify_fh_ext_buf(&fne->dir_fh));
- kfree(fne);
+ kfree(FANOTIFY_NE(event));
}
static void fanotify_free_event(struct fsnotify_event *fsn_event)
@@ -640,6 +788,9 @@ static void fanotify_free_event(struct fsnotify_event *fsn_event)
case FANOTIFY_EVENT_TYPE_FID_NAME:
fanotify_free_name_event(event);
break;
+ case FANOTIFY_EVENT_TYPE_OVERFLOW:
+ kfree(event);
+ break;
default:
WARN_ON_ONCE(1);
}
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index 8ce7ccfc4b0d..896c819a1786 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -23,20 +23,41 @@ enum {
* stored in either the first or last 2 dwords.
*/
#define FANOTIFY_INLINE_FH_LEN (3 << 2)
+#define FANOTIFY_FH_HDR_LEN offsetof(struct fanotify_fh, buf)
+/* Fixed size struct for file handle */
struct fanotify_fh {
- unsigned char buf[FANOTIFY_INLINE_FH_LEN];
u8 type;
u8 len;
+#define FANOTIFY_FH_FLAG_EXT_BUF 1
+ u8 flags;
+ u8 pad;
+ unsigned char buf[];
+} __aligned(4);
+
+/* Variable size struct for dir file handle + child file handle + name */
+struct fanotify_info {
+ /* size of dir_fh/file_fh including fanotify_fh hdr size */
+ u8 dir_fh_totlen;
+ u8 file_fh_totlen;
+ u8 name_len;
+ u8 pad;
+ unsigned char buf[];
+ /*
+ * (struct fanotify_fh) dir_fh starts at buf[0]
+ * (optional) file_fh starts at buf[dir_fh_totlen]
+ * name starts at buf[dir_fh_totlen + file_fh_totlen]
+ */
} __aligned(4);
static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh)
{
- return fh->len > FANOTIFY_INLINE_FH_LEN;
+ return (fh->flags & FANOTIFY_FH_FLAG_EXT_BUF);
}
static inline char **fanotify_fh_ext_buf_ptr(struct fanotify_fh *fh)
{
+ BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN % 4);
BUILD_BUG_ON(__alignof__(char *) - 4 + sizeof(char *) >
FANOTIFY_INLINE_FH_LEN);
return (char **)ALIGN((unsigned long)(fh->buf), __alignof__(char *));
@@ -52,6 +73,56 @@ static inline void *fanotify_fh_buf(struct fanotify_fh *fh)
return fanotify_fh_has_ext_buf(fh) ? fanotify_fh_ext_buf(fh) : fh->buf;
}
+static inline int fanotify_info_dir_fh_len(struct fanotify_info *info)
+{
+ if (!info->dir_fh_totlen ||
+ WARN_ON_ONCE(info->dir_fh_totlen < FANOTIFY_FH_HDR_LEN))
+ return 0;
+
+ return info->dir_fh_totlen - FANOTIFY_FH_HDR_LEN;
+}
+
+static inline struct fanotify_fh *fanotify_info_dir_fh(struct fanotify_info *info)
+{
+ BUILD_BUG_ON(offsetof(struct fanotify_info, buf) % 4);
+
+ return (struct fanotify_fh *)info->buf;
+}
+
+static inline int fanotify_info_file_fh_len(struct fanotify_info *info)
+{
+ if (!info->file_fh_totlen ||
+ WARN_ON_ONCE(info->file_fh_totlen < FANOTIFY_FH_HDR_LEN))
+ return 0;
+
+ return info->file_fh_totlen - FANOTIFY_FH_HDR_LEN;
+}
+
+static inline struct fanotify_fh *fanotify_info_file_fh(struct fanotify_info *info)
+{
+ return (struct fanotify_fh *)(info->buf + info->dir_fh_totlen);
+}
+
+static inline const char *fanotify_info_name(struct fanotify_info *info)
+{
+ return info->buf + info->dir_fh_totlen + info->file_fh_totlen;
+}
+
+static inline void fanotify_info_init(struct fanotify_info *info)
+{
+ info->dir_fh_totlen = 0;
+ info->file_fh_totlen = 0;
+ info->name_len = 0;
+}
+
+static inline void fanotify_info_copy_name(struct fanotify_info *info,
+ const struct qstr *name)
+{
+ info->name_len = name->len;
+ strcpy(info->buf + info->dir_fh_totlen + info->file_fh_totlen,
+ name->name);
+}
+
/*
* Common structure for fanotify events. Concrete structs are allocated in
* fanotify_handle_event() and freed when the information is retrieved by
@@ -63,6 +134,7 @@ enum fanotify_event_type {
FANOTIFY_EVENT_TYPE_FID_NAME, /* variable length */
FANOTIFY_EVENT_TYPE_PATH,
FANOTIFY_EVENT_TYPE_PATH_PERM,
+ FANOTIFY_EVENT_TYPE_OVERFLOW, /* struct fanotify_event */
};
struct fanotify_event {
@@ -72,10 +144,20 @@ struct fanotify_event {
struct pid *pid;
};
+static inline void fanotify_init_event(struct fanotify_event *event,
+ unsigned long id, u32 mask)
+{
+ fsnotify_init_event(&event->fse, id);
+ event->mask = mask;
+ event->pid = NULL;
+}
+
struct fanotify_fid_event {
struct fanotify_event fae;
__kernel_fsid_t fsid;
struct fanotify_fh object_fh;
+ /* Reserve space in object_fh.buf[] - access with fanotify_fh_buf() */
+ unsigned char _inline_fh_buf[FANOTIFY_INLINE_FH_LEN];
};
static inline struct fanotify_fid_event *
@@ -87,9 +169,7 @@ FANOTIFY_FE(struct fanotify_event *event)
struct fanotify_name_event {
struct fanotify_event fae;
__kernel_fsid_t fsid;
- struct fanotify_fh dir_fh;
- u8 name_len;
- char name[];
+ struct fanotify_info info;
};
static inline struct fanotify_name_event *
@@ -113,35 +193,37 @@ static inline struct fanotify_fh *fanotify_event_object_fh(
{
if (event->type == FANOTIFY_EVENT_TYPE_FID)
return &FANOTIFY_FE(event)->object_fh;
+ else if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME)
+ return fanotify_info_file_fh(&FANOTIFY_NE(event)->info);
else
return NULL;
}
-static inline struct fanotify_fh *fanotify_event_dir_fh(
+static inline struct fanotify_info *fanotify_event_info(
struct fanotify_event *event)
{
if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME)
- return &FANOTIFY_NE(event)->dir_fh;
+ return &FANOTIFY_NE(event)->info;
else
return NULL;
}
static inline int fanotify_event_object_fh_len(struct fanotify_event *event)
{
+ struct fanotify_info *info = fanotify_event_info(event);
struct fanotify_fh *fh = fanotify_event_object_fh(event);
- return fh ? fh->len : 0;
+ if (info)
+ return info->file_fh_totlen ? fh->len : 0;
+ else
+ return fh ? fh->len : 0;
}
-static inline bool fanotify_event_has_name(struct fanotify_event *event)
+static inline int fanotify_event_dir_fh_len(struct fanotify_event *event)
{
- return event->type == FANOTIFY_EVENT_TYPE_FID_NAME;
-}
+ struct fanotify_info *info = fanotify_event_info(event);
-static inline int fanotify_event_name_len(struct fanotify_event *event)
-{
- return fanotify_event_has_name(event) ?
- FANOTIFY_NE(event)->name_len : 0;
+ return info ? fanotify_info_dir_fh_len(info) : 0;
}
struct fanotify_path_event {
@@ -202,9 +284,3 @@ static inline struct path *fanotify_event_path(struct fanotify_event *event)
else
return NULL;
}
-
-struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
- struct inode *inode, u32 mask,
- const void *data, int data_type,
- const struct qstr *file_name,
- __kernel_fsid_t *fsid);
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 63b5dffdca9e..559de311deca 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -64,21 +64,28 @@ static int fanotify_fid_info_len(int fh_len, int name_len)
return roundup(FANOTIFY_INFO_HDR_LEN + info_len, FANOTIFY_EVENT_ALIGN);
}
-static int fanotify_event_info_len(struct fanotify_event *event)
+static int fanotify_event_info_len(unsigned int fid_mode,
+ struct fanotify_event *event)
{
- int info_len = 0;
+ struct fanotify_info *info = fanotify_event_info(event);
+ int dir_fh_len = fanotify_event_dir_fh_len(event);
int fh_len = fanotify_event_object_fh_len(event);
+ int info_len = 0;
+ int dot_len = 0;
- if (fh_len)
- info_len += fanotify_fid_info_len(fh_len, 0);
-
- if (fanotify_event_name_len(event)) {
- struct fanotify_name_event *fne = FANOTIFY_NE(event);
-
- info_len += fanotify_fid_info_len(fne->dir_fh.len,
- fne->name_len);
+ if (dir_fh_len) {
+ info_len += fanotify_fid_info_len(dir_fh_len, info->name_len);
+ } else if ((fid_mode & FAN_REPORT_NAME) && (event->mask & FAN_ONDIR)) {
+ /*
+ * With group flag FAN_REPORT_NAME, if name was not recorded in
+ * event on a directory, we will report the name ".".
+ */
+ dot_len = 1;
}
+ if (fh_len)
+ info_len += fanotify_fid_info_len(fh_len, dot_len);
+
return info_len;
}
@@ -93,6 +100,7 @@ static struct fanotify_event *get_one_event(struct fsnotify_group *group,
{
size_t event_size = FAN_EVENT_METADATA_LEN;
struct fanotify_event *event = NULL;
+ unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
pr_debug("%s: group=%p count=%zd\n", __func__, group, count);
@@ -100,8 +108,8 @@ static struct fanotify_event *get_one_event(struct fsnotify_group *group,
if (fsnotify_notify_queue_is_empty(group))
goto out;
- if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
- event_size += fanotify_event_info_len(
+ if (fid_mode) {
+ event_size += fanotify_event_info_len(fid_mode,
FANOTIFY_E(fsnotify_peek_first_event(group)));
}
@@ -218,7 +226,7 @@ static int process_access_response(struct fsnotify_group *group,
}
static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
- const char *name, size_t name_len,
+ int info_type, const char *name, size_t name_len,
char __user *buf, size_t count)
{
struct fanotify_event_info_fid info = { };
@@ -231,7 +239,7 @@ static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
pr_debug("%s: fh_len=%zu name_len=%zu, info_len=%zu, count=%zu\n",
__func__, fh_len, name_len, info_len, count);
- if (!fh_len || (name && !name_len))
+ if (!fh_len)
return 0;
if (WARN_ON_ONCE(len < sizeof(info) || len > count))
@@ -241,8 +249,21 @@ static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
* Copy event info fid header followed by variable sized file handle
* and optionally followed by variable sized filename.
*/
- info.hdr.info_type = name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME :
- FAN_EVENT_INFO_TYPE_FID;
+ switch (info_type) {
+ case FAN_EVENT_INFO_TYPE_FID:
+ case FAN_EVENT_INFO_TYPE_DFID:
+ if (WARN_ON_ONCE(name_len))
+ return -EFAULT;
+ break;
+ case FAN_EVENT_INFO_TYPE_DFID_NAME:
+ if (WARN_ON_ONCE(!name || !name_len))
+ return -EFAULT;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ info.hdr.info_type = info_type;
info.hdr.len = len;
info.fsid = *fsid;
if (copy_to_user(buf, &info, sizeof(info)))
@@ -305,13 +326,16 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
{
struct fanotify_event_metadata metadata;
struct path *path = fanotify_event_path(event);
+ struct fanotify_info *info = fanotify_event_info(event);
+ unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
struct file *f = NULL;
int ret, fd = FAN_NOFD;
+ int info_type = 0;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
metadata.event_len = FAN_EVENT_METADATA_LEN +
- fanotify_event_info_len(event);
+ fanotify_event_info_len(fid_mode, event);
metadata.metadata_len = FAN_EVENT_METADATA_LEN;
metadata.vers = FANOTIFY_METADATA_VERSION;
metadata.reserved = 0;
@@ -346,13 +370,13 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
fd_install(fd, f);
/* Event info records order is: dir fid + name, child fid */
- if (fanotify_event_name_len(event)) {
- struct fanotify_name_event *fne = FANOTIFY_NE(event);
-
+ if (fanotify_event_dir_fh_len(event)) {
+ info_type = info->name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME :
+ FAN_EVENT_INFO_TYPE_DFID;
ret = copy_info_to_user(fanotify_event_fsid(event),
- fanotify_event_dir_fh(event),
- fne->name, fne->name_len,
- buf, count);
+ fanotify_info_dir_fh(info),
+ info_type, fanotify_info_name(info),
+ info->name_len, buf, count);
if (ret < 0)
return ret;
@@ -361,9 +385,46 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
}
if (fanotify_event_object_fh_len(event)) {
+ const char *dot = NULL;
+ int dot_len = 0;
+
+ if (fid_mode == FAN_REPORT_FID || info_type) {
+ /*
+ * With only group flag FAN_REPORT_FID only type FID is
+ * reported. Second info record type is always FID.
+ */
+ info_type = FAN_EVENT_INFO_TYPE_FID;
+ } else if ((fid_mode & FAN_REPORT_NAME) &&
+ (event->mask & FAN_ONDIR)) {
+ /*
+ * With group flag FAN_REPORT_NAME, if name was not
+ * recorded in an event on a directory, report the
+ * name "." with info type DFID_NAME.
+ */
+ info_type = FAN_EVENT_INFO_TYPE_DFID_NAME;
+ dot = ".";
+ dot_len = 1;
+ } else if ((event->mask & ALL_FSNOTIFY_DIRENT_EVENTS) ||
+ (event->mask & FAN_ONDIR)) {
+ /*
+ * With group flag FAN_REPORT_DIR_FID, a single info
+ * record has type DFID for directory entry modification
+ * event and for event on a directory.
+ */
+ info_type = FAN_EVENT_INFO_TYPE_DFID;
+ } else {
+ /*
+ * With group flags FAN_REPORT_DIR_FID|FAN_REPORT_FID,
+ * a single info record has type FID for event on a
+ * non-directory, when there is no directory to report.
+ * For example, on FAN_DELETE_SELF event.
+ */
+ info_type = FAN_EVENT_INFO_TYPE_FID;
+ }
+
ret = copy_info_to_user(fanotify_event_fsid(event),
fanotify_event_object_fh(event),
- NULL, 0, buf, count);
+ info_type, dot, dot_len, buf, count);
if (ret < 0)
return ret;
@@ -412,6 +473,11 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
add_wait_queue(&group->notification_waitq, &wait);
while (1) {
+ /*
+ * User can supply arbitrarily large buffer. Avoid softlockups
+ * in case there are lots of available events.
+ */
+ cond_resched();
event = get_one_event(group, count);
if (IS_ERR(event)) {
ret = PTR_ERR(event);
@@ -651,12 +717,13 @@ out:
}
static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
- __u32 mask,
- unsigned int flags,
- int *destroy)
+ __u32 mask, unsigned int flags,
+ __u32 umask, int *destroy)
{
__u32 oldmask = 0;
+ /* umask bits cannot be removed by user */
+ mask &= ~umask;
spin_lock(&fsn_mark->lock);
if (!(flags & FAN_MARK_IGNORED_MASK)) {
oldmask = fsn_mark->mask;
@@ -664,7 +731,13 @@ static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
} else {
fsn_mark->ignored_mask &= ~mask;
}
- *destroy = !(fsn_mark->mask | fsn_mark->ignored_mask);
+ /*
+ * We need to keep the mark around even if remaining mask cannot
+ * result in any events (e.g. mask == FAN_ONDIR) to support incremenal
+ * changes to the mask.
+ * Destroy mark when only umask bits remain.
+ */
+ *destroy = !((fsn_mark->mask | fsn_mark->ignored_mask) & ~umask);
spin_unlock(&fsn_mark->lock);
return mask & oldmask;
@@ -672,7 +745,7 @@ static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
static int fanotify_remove_mark(struct fsnotify_group *group,
fsnotify_connp_t *connp, __u32 mask,
- unsigned int flags)
+ unsigned int flags, __u32 umask)
{
struct fsnotify_mark *fsn_mark = NULL;
__u32 removed;
@@ -686,7 +759,7 @@ static int fanotify_remove_mark(struct fsnotify_group *group,
}
removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags,
- &destroy_mark);
+ umask, &destroy_mark);
if (removed & fsnotify_conn_mask(fsn_mark->connector))
fsnotify_recalc_mask(fsn_mark->connector);
if (destroy_mark)
@@ -702,25 +775,26 @@ static int fanotify_remove_mark(struct fsnotify_group *group,
static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group,
struct vfsmount *mnt, __u32 mask,
- unsigned int flags)
+ unsigned int flags, __u32 umask)
{
return fanotify_remove_mark(group, &real_mount(mnt)->mnt_fsnotify_marks,
- mask, flags);
+ mask, flags, umask);
}
static int fanotify_remove_sb_mark(struct fsnotify_group *group,
- struct super_block *sb, __u32 mask,
- unsigned int flags)
+ struct super_block *sb, __u32 mask,
+ unsigned int flags, __u32 umask)
{
- return fanotify_remove_mark(group, &sb->s_fsnotify_marks, mask, flags);
+ return fanotify_remove_mark(group, &sb->s_fsnotify_marks, mask,
+ flags, umask);
}
static int fanotify_remove_inode_mark(struct fsnotify_group *group,
struct inode *inode, __u32 mask,
- unsigned int flags)
+ unsigned int flags, __u32 umask)
{
return fanotify_remove_mark(group, &inode->i_fsnotify_marks, mask,
- flags);
+ flags, umask);
}
static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
@@ -831,13 +905,28 @@ static int fanotify_add_inode_mark(struct fsnotify_group *group,
FSNOTIFY_OBJ_TYPE_INODE, mask, flags, fsid);
}
+static struct fsnotify_event *fanotify_alloc_overflow_event(void)
+{
+ struct fanotify_event *oevent;
+
+ oevent = kmalloc(sizeof(*oevent), GFP_KERNEL_ACCOUNT);
+ if (!oevent)
+ return NULL;
+
+ fanotify_init_event(oevent, 0, FS_Q_OVERFLOW);
+ oevent->type = FANOTIFY_EVENT_TYPE_OVERFLOW;
+
+ return &oevent->fse;
+}
+
/* fanotify syscalls */
SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
{
struct fsnotify_group *group;
int f_flags, fd;
struct user_struct *user;
- struct fanotify_event *oevent;
+ unsigned int fid_mode = flags & FANOTIFY_FID_BITS;
+ unsigned int class = flags & FANOTIFY_CLASS_BITS;
pr_debug("%s: flags=%x event_f_flags=%x\n",
__func__, flags, event_f_flags);
@@ -864,8 +953,14 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
return -EINVAL;
}
- if ((flags & FAN_REPORT_FID) &&
- (flags & FANOTIFY_CLASS_BITS) != FAN_CLASS_NOTIF)
+ if (fid_mode && class != FAN_CLASS_NOTIF)
+ return -EINVAL;
+
+ /*
+ * Child name is reported with parent fid so requires dir fid.
+ * We can report both child fid and dir fid with or without name.
+ */
+ if ((fid_mode & FAN_REPORT_NAME) && !(fid_mode & FAN_REPORT_DIR_FID))
return -EINVAL;
user = get_current_user();
@@ -892,20 +987,18 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
atomic_inc(&user->fanotify_listeners);
group->memcg = get_mem_cgroup_from_mm(current->mm);
- oevent = fanotify_alloc_event(group, NULL, FS_Q_OVERFLOW, NULL,
- FSNOTIFY_EVENT_NONE, NULL, NULL);
- if (unlikely(!oevent)) {
+ group->overflow_event = fanotify_alloc_overflow_event();
+ if (unlikely(!group->overflow_event)) {
fd = -ENOMEM;
goto out_destroy_group;
}
- group->overflow_event = &oevent->fse;
if (force_o_largefile())
event_f_flags |= O_LARGEFILE;
group->fanotify_data.f_flags = event_f_flags;
init_waitqueue_head(&group->fanotify_data.access_waitq);
INIT_LIST_HEAD(&group->fanotify_data.access_list);
- switch (flags & FANOTIFY_CLASS_BITS) {
+ switch (class) {
case FAN_CLASS_NOTIF:
group->priority = FS_PRIO_0;
break;
@@ -1024,7 +1117,9 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
__kernel_fsid_t __fsid, *fsid = NULL;
u32 valid_mask = FANOTIFY_EVENTS | FANOTIFY_EVENT_FLAGS;
unsigned int mark_type = flags & FANOTIFY_MARK_TYPE_BITS;
- unsigned int obj_type;
+ bool ignored = flags & FAN_MARK_IGNORED_MASK;
+ unsigned int obj_type, fid_mode;
+ u32 umask = 0;
int ret;
pr_debug("%s: fanotify_fd=%d flags=%x dfd=%d pathname=%p mask=%llx\n",
@@ -1071,6 +1166,10 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
if (mask & ~valid_mask)
return -EINVAL;
+ /* Event flags (ONDIR, ON_CHILD) are meaningless in ignored mask */
+ if (ignored)
+ mask &= ~FANOTIFY_EVENT_FLAGS;
+
f = fdget(fanotify_fd);
if (unlikely(!f.file))
return -EBADF;
@@ -1097,9 +1196,9 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
* inode events are not supported on a mount mark, because they do not
* carry enough information (i.e. path) to be filtered by mount point.
*/
+ fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
if (mask & FANOTIFY_INODE_EVENTS &&
- (!FAN_GROUP_FLAG(group, FAN_REPORT_FID) ||
- mark_type == FAN_MARK_MOUNT))
+ (!fid_mode || mark_type == FAN_MARK_MOUNT))
goto fput_and_out;
if (flags & FAN_MARK_FLUSH) {
@@ -1124,7 +1223,7 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
goto path_put_and_out;
}
- if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
+ if (fid_mode) {
ret = fanotify_test_fid(&path, &__fsid);
if (ret)
goto path_put_and_out;
@@ -1138,6 +1237,19 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
else
mnt = path.mnt;
+ /* Mask out FAN_EVENT_ON_CHILD flag for sb/mount/non-dir marks */
+ if (mnt || !S_ISDIR(inode->i_mode)) {
+ mask &= ~FAN_EVENT_ON_CHILD;
+ umask = FAN_EVENT_ON_CHILD;
+ /*
+ * If group needs to report parent fid, register for getting
+ * events with parent/name info for non-directory.
+ */
+ if ((fid_mode & FAN_REPORT_DIR_FID) &&
+ (flags & FAN_MARK_ADD) && !ignored)
+ mask |= FAN_EVENT_ON_CHILD;
+ }
+
/* create/update an inode mark */
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
case FAN_MARK_ADD:
@@ -1154,13 +1266,13 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
case FAN_MARK_REMOVE:
if (mark_type == FAN_MARK_MOUNT)
ret = fanotify_remove_vfsmount_mark(group, mnt, mask,
- flags);
+ flags, umask);
else if (mark_type == FAN_MARK_FILESYSTEM)
ret = fanotify_remove_sb_mark(group, mnt->mnt_sb, mask,
- flags);
+ flags, umask);
else
ret = fanotify_remove_inode_mark(group, inode, mask,
- flags);
+ flags, umask);
break;
default:
ret = -EINVAL;
@@ -1203,7 +1315,7 @@ COMPAT_SYSCALL_DEFINE6(fanotify_mark,
*/
static int __init fanotify_user_setup(void)
{
- BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 8);
+ BUILD_BUG_ON(HWEIGHT32(FANOTIFY_INIT_FLAGS) != 10);
BUILD_BUG_ON(HWEIGHT32(FANOTIFY_MARK_FLAGS) != 9);
fanotify_mark_cache = KMEM_CACHE(fsnotify_mark,
diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
index 72d332ce8e12..a960ec3a569a 100644
--- a/fs/notify/fsnotify.c
+++ b/fs/notify/fsnotify.c
@@ -74,7 +74,7 @@ static void fsnotify_unmount_inodes(struct super_block *sb)
iput(iput_inode);
/* for each watch, send FS_UNMOUNT and then remove it */
- fsnotify(inode, FS_UNMOUNT, inode, FSNOTIFY_EVENT_INODE, NULL, 0);
+ fsnotify_inode(inode, FS_UNMOUNT);
fsnotify_inode_delete(inode);
@@ -142,45 +142,140 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
spin_unlock(&inode->i_lock);
}
-/* Notify this dentry's parent about a child's events. */
-int fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
- int data_type)
+/* 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 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;
- struct inode *p_inode;
+ 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;
- if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED))
+ /*
+ * 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 (!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) {
- struct name_snapshot name;
- /* we are notifying a parent so come up with the new mask which
- * specifies these are events which came from a child. */
- mask |= FS_EVENT_ON_CHILD;
+ /*
+ * 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);
- ret = fsnotify(p_inode, mask, data, data_type, &name.name, 0);
- release_dentry_name_snapshot(&name);
+ file_name = &name.name;
+ if (parent_watched)
+ mask |= FS_EVENT_ON_CHILD;
}
+notify:
+ ret = fsnotify(mask, data, data_type, p_inode, file_name, inode, 0);
+
+ if (file_name)
+ release_dentry_name_snapshot(&name);
dput(parent);
return ret;
}
-EXPORT_SYMBOL_GPL(fsnotify_parent);
+EXPORT_SYMBOL_GPL(__fsnotify_parent);
+
+static int fsnotify_handle_event(struct fsnotify_group *group, __u32 mask,
+ const void *data, int data_type,
+ struct inode *dir, const struct qstr *name,
+ u32 cookie, struct fsnotify_iter_info *iter_info)
+{
+ struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
+ struct fsnotify_mark *child_mark = fsnotify_iter_child_mark(iter_info);
+ struct inode *inode = fsnotify_data_inode(data, data_type);
+ const struct fsnotify_ops *ops = group->ops;
+ int ret;
+
+ if (WARN_ON_ONCE(!ops->handle_inode_event))
+ return 0;
+
+ if (WARN_ON_ONCE(fsnotify_iter_sb_mark(iter_info)) ||
+ WARN_ON_ONCE(fsnotify_iter_vfsmount_mark(iter_info)))
+ return 0;
+
+ /*
+ * An event can be sent on child mark iterator instead of inode mark
+ * iterator because of other groups that have interest of this inode
+ * and have marks on both parent and child. We can simplify this case.
+ */
+ if (!inode_mark) {
+ inode_mark = child_mark;
+ child_mark = NULL;
+ dir = NULL;
+ name = NULL;
+ }
+
+ ret = ops->handle_inode_event(inode_mark, mask, inode, dir, name);
+ if (ret || !child_mark)
+ return ret;
+
+ /*
+ * Some events can be sent on both parent dir and child marks
+ * (e.g. FS_ATTRIB). If both parent dir and child are watching,
+ * report the event once to parent dir with name and once to child
+ * without name.
+ */
+ return ops->handle_inode_event(child_mark, mask, inode, NULL, NULL);
+}
-static int send_to_group(struct inode *to_tell,
- __u32 mask, const void *data,
- int data_is, u32 cookie,
- const struct qstr *file_name,
- struct fsnotify_iter_info *iter_info)
+static int send_to_group(__u32 mask, const void *data, int data_type,
+ struct inode *dir, const struct qstr *file_name,
+ u32 cookie, struct fsnotify_iter_info *iter_info)
{
struct fsnotify_group *group = NULL;
__u32 test_mask = (mask & ALL_FSNOTIFY_EVENTS);
@@ -216,16 +311,20 @@ static int send_to_group(struct inode *to_tell,
}
}
- pr_debug("%s: group=%p to_tell=%p mask=%x marks_mask=%x marks_ignored_mask=%x"
- " data=%p data_is=%d cookie=%d\n",
- __func__, group, to_tell, mask, marks_mask, marks_ignored_mask,
- data, data_is, cookie);
+ pr_debug("%s: group=%p mask=%x marks_mask=%x marks_ignored_mask=%x data=%p data_type=%d dir=%p cookie=%d\n",
+ __func__, group, mask, marks_mask, marks_ignored_mask,
+ data, data_type, dir, cookie);
if (!(test_mask & marks_mask & ~marks_ignored_mask))
return 0;
- return group->ops->handle_event(group, to_tell, mask, data, data_is,
- file_name, cookie, iter_info);
+ if (group->ops->handle_event) {
+ return group->ops->handle_event(group, mask, data, data_type, dir,
+ file_name, cookie, iter_info);
+ }
+
+ return fsnotify_handle_event(group, mask, data, data_type, dir,
+ file_name, cookie, iter_info);
}
static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector **connp)
@@ -303,29 +402,51 @@ static void fsnotify_iter_next(struct fsnotify_iter_info *iter_info)
}
/*
- * This is the main call to fsnotify. The VFS calls into hook specific functions
- * in linux/fsnotify.h. Those functions then in turn call here. Here will call
- * out to all of the registered fsnotify_group. Those groups can then use the
- * notification event in whatever means they feel necessary.
+ * fsnotify - This is the main call to fsnotify.
+ *
+ * The VFS calls into hook specific functions in linux/fsnotify.h.
+ * Those functions then in turn call here. Here will call out to all of the
+ * registered fsnotify_group. Those groups can then use the notification event
+ * in whatever means they feel necessary.
+ *
+ * @mask: event type and flags
+ * @data: object that event happened on
+ * @data_type: type of object for fanotify_data_XXX() accessors
+ * @dir: optional directory associated with event -
+ * if @file_name is not NULL, this is the directory that
+ * @file_name is relative to
+ * @file_name: optional file name associated with event
+ * @inode: optional inode associated with event -
+ * either @dir or @inode must be non-NULL.
+ * if both are non-NULL event may be reported to both.
+ * @cookie: inotify rename cookie
*/
-int fsnotify(struct inode *to_tell, __u32 mask, const void *data, int data_is,
- const struct qstr *file_name, u32 cookie)
+int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
+ const struct qstr *file_name, struct inode *inode, u32 cookie)
{
- const struct path *path = fsnotify_data_path(data, data_is);
+ const struct path *path = fsnotify_data_path(data, data_type);
struct fsnotify_iter_info iter_info = {};
- struct super_block *sb = to_tell->i_sb;
+ struct super_block *sb;
struct mount *mnt = NULL;
- __u32 mnt_or_sb_mask = sb->s_fsnotify_mask;
+ struct inode *child = NULL;
int ret = 0;
- __u32 test_mask = (mask & ALL_FSNOTIFY_EVENTS);
+ __u32 test_mask, marks_mask;
- if (path) {
+ if (path)
mnt = real_mount(path->mnt);
- mnt_or_sb_mask |= mnt->mnt_fsnotify_mask;
+
+ if (!inode) {
+ /* Dirent event - report on TYPE_INODE to dir */
+ inode = dir;
+ } else if (mask & FS_EVENT_ON_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;
}
- /* An event "on child" is not intended for a mount/sb mark */
- if (mask & FS_EVENT_ON_CHILD)
- mnt_or_sb_mask = 0;
+ sb = inode->i_sb;
/*
* Optimization: srcu_read_lock() has a memory barrier which can
@@ -334,28 +455,45 @@ int fsnotify(struct inode *to_tell, __u32 mask, const void *data, int data_is,
* SRCU because we have no references to any objects and do not
* need SRCU to keep them "alive".
*/
- if (!to_tell->i_fsnotify_marks && !sb->s_fsnotify_marks &&
- (!mnt || !mnt->mnt_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 = 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;
+
+
/*
* if this is a modify event we may need to clear the ignored masks
- * otherwise return if neither the inode nor the vfsmount/sb care about
- * this type of event.
+ * otherwise return if none of the marks care about this type of event.
*/
- if (!(mask & FS_MODIFY) &&
- !(test_mask & (to_tell->i_fsnotify_mask | mnt_or_sb_mask)))
+ test_mask = (mask & ALL_FSNOTIFY_EVENTS);
+ if (!(mask & FS_MODIFY) && !(test_mask & marks_mask))
return 0;
iter_info.srcu_idx = srcu_read_lock(&fsnotify_mark_srcu);
- iter_info.marks[FSNOTIFY_OBJ_TYPE_INODE] =
- fsnotify_first_mark(&to_tell->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);
+ }
/*
* We need to merge inode/vfsmount/sb mark lists so that e.g. inode mark
@@ -363,8 +501,8 @@ int fsnotify(struct inode *to_tell, __u32 mask, const void *data, int data_is,
* That's why this traversal is so complicated...
*/
while (fsnotify_iter_select_report_types(&iter_info)) {
- ret = send_to_group(to_tell, mask, data, data_is, cookie,
- file_name, &iter_info);
+ ret = send_to_group(mask, data, data_type, dir, file_name,
+ cookie, &iter_info);
if (ret && (mask & ALL_FSNOTIFY_PERM_EVENTS))
goto out;
@@ -383,7 +521,7 @@ static __init int fsnotify_init(void)
{
int ret;
- BUILD_BUG_ON(HWEIGHT32(ALL_FSNOTIFY_BITS) != 26);
+ BUILD_BUG_ON(HWEIGHT32(ALL_FSNOTIFY_BITS) != 25);
ret = init_srcu_struct(&fsnotify_mark_srcu);
if (ret)
diff --git a/fs/notify/inotify/inotify.h b/fs/notify/inotify/inotify.h
index 3f246f7b8a92..4327d0e9c364 100644
--- a/fs/notify/inotify/inotify.h
+++ b/fs/notify/inotify/inotify.h
@@ -24,9 +24,9 @@ static inline struct inotify_event_info *INOTIFY_E(struct fsnotify_event *fse)
extern void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
struct fsnotify_group *group);
-extern int inotify_handle_event(struct fsnotify_group *group,
- struct inode *inode,
- u32 mask, const void *data, int data_type,
+extern int inotify_handle_event(struct fsnotify_group *group, u32 mask,
+ const void *data, int data_type,
+ struct inode *dir,
const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info);
diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c
index 2ebc89047153..a65cf8c9f600 100644
--- a/fs/notify/inotify/inotify_fsnotify.c
+++ b/fs/notify/inotify/inotify_fsnotify.c
@@ -39,7 +39,7 @@ static bool event_compare(struct fsnotify_event *old_fsn,
if (old->mask & FS_IN_IGNORED)
return false;
if ((old->mask == new->mask) &&
- (old_fsn->objectid == new_fsn->objectid) &&
+ (old->wd == new->wd) &&
(old->name_len == new->name_len) &&
(!old->name_len || !strcmp(old->name, new->name)))
return true;
@@ -55,14 +55,11 @@ static int inotify_merge(struct list_head *list,
return event_compare(last_event, event);
}
-int inotify_handle_event(struct fsnotify_group *group,
- struct inode *inode,
- u32 mask, const void *data, int data_type,
- const struct qstr *file_name, u32 cookie,
- struct fsnotify_iter_info *iter_info)
+static int inotify_one_event(struct fsnotify_group *group, u32 mask,
+ struct fsnotify_mark *inode_mark,
+ const struct path *path,
+ const struct qstr *file_name, u32 cookie)
{
- const struct path *path = fsnotify_data_path(data, data_type);
- struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
struct inotify_inode_mark *i_mark;
struct inotify_event_info *event;
struct fsnotify_event *fsn_event;
@@ -70,9 +67,6 @@ int inotify_handle_event(struct fsnotify_group *group,
int len = 0;
int alloc_len = sizeof(struct inotify_event_info);
- if (WARN_ON(fsnotify_iter_vfsmount_mark(iter_info)))
- return 0;
-
if ((inode_mark->mask & FS_EXCL_UNLINK) &&
path && d_unlinked(path->dentry))
return 0;
@@ -82,7 +76,7 @@ int inotify_handle_event(struct fsnotify_group *group,
alloc_len += len + 1;
}
- pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode,
+ pr_debug("%s: group=%p mark=%p mask=%x\n", __func__, group, inode_mark,
mask);
i_mark = container_of(inode_mark, struct inotify_inode_mark,
@@ -116,7 +110,7 @@ int inotify_handle_event(struct fsnotify_group *group,
mask &= ~IN_ISDIR;
fsn_event = &event->fse;
- fsnotify_init_event(fsn_event, (unsigned long)inode);
+ fsnotify_init_event(fsn_event, 0);
event->mask = mask;
event->wd = i_mark->wd;
event->sync_cookie = cookie;
@@ -136,6 +130,37 @@ int inotify_handle_event(struct fsnotify_group *group,
return 0;
}
+int inotify_handle_event(struct fsnotify_group *group, u32 mask,
+ const void *data, int data_type, struct inode *dir,
+ const struct qstr *file_name, u32 cookie,
+ struct fsnotify_iter_info *iter_info)
+{
+ const struct path *path = fsnotify_data_path(data, data_type);
+ struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
+ struct fsnotify_mark *child_mark = fsnotify_iter_child_mark(iter_info);
+ int ret = 0;
+
+ if (WARN_ON(fsnotify_iter_vfsmount_mark(iter_info)))
+ return 0;
+
+ /*
+ * Some events cannot be sent on both parent and child marks
+ * (e.g. IN_CREATE). Those events are always sent on inode_mark.
+ * For events that are possible on both parent and child (e.g. IN_OPEN),
+ * event is sent on inode_mark with name if the parent is watching and
+ * is sent on child_mark without name if child is watching.
+ * If both parent and child are watching, report the event with child's
+ * name here and report another event without child's name below.
+ */
+ if (inode_mark)
+ ret = inotify_one_event(group, mask, inode_mark, path,
+ file_name, cookie);
+ if (ret || !child_mark)
+ return ret;
+
+ return inotify_one_event(group, mask, child_mark, path, NULL, 0);
+}
+
static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group)
{
inotify_ignored_and_remove_idr(fsn_mark, group);
diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c
index f88bbcc9efeb..186722ba3894 100644
--- a/fs/notify/inotify/inotify_user.c
+++ b/fs/notify/inotify/inotify_user.c
@@ -75,15 +75,17 @@ struct ctl_table inotify_table[] = {
};
#endif /* CONFIG_SYSCTL */
-static inline __u32 inotify_arg_to_mask(u32 arg)
+static inline __u32 inotify_arg_to_mask(struct inode *inode, u32 arg)
{
__u32 mask;
/*
- * everything should accept their own ignored, cares about children,
- * and should receive events when the inode is unmounted
+ * Everything should accept their own ignored and should receive events
+ * when the inode is unmounted. All directories care about children.
*/
- mask = (FS_IN_IGNORED | FS_EVENT_ON_CHILD | FS_UNMOUNT);
+ mask = (FS_IN_IGNORED | FS_UNMOUNT);
+ if (S_ISDIR(inode->i_mode))
+ mask |= FS_EVENT_ON_CHILD;
/* mask off the flags used to open the fd */
mask |= (arg & (IN_ALL_EVENTS | IN_ONESHOT | IN_EXCL_UNLINK));
@@ -490,8 +492,8 @@ void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
fsn_mark);
/* Queue ignore event for the watch */
- inotify_handle_event(group, NULL, FS_IN_IGNORED, NULL,
- FSNOTIFY_EVENT_NONE, NULL, 0, &iter_info);
+ inotify_handle_event(group, FS_IN_IGNORED, NULL, FSNOTIFY_EVENT_NONE,
+ NULL, NULL, 0, &iter_info);
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
/* remove this mark from the idr */
@@ -512,7 +514,7 @@ static int inotify_update_existing_watch(struct fsnotify_group *group,
int create = (arg & IN_MASK_CREATE);
int ret;
- mask = inotify_arg_to_mask(arg);
+ mask = inotify_arg_to_mask(inode, arg);
fsn_mark = fsnotify_find_mark(&inode->i_fsnotify_marks, group);
if (!fsn_mark)
@@ -565,7 +567,7 @@ static int inotify_new_watch(struct fsnotify_group *group,
struct idr *idr = &group->inotify_data.idr;
spinlock_t *idr_lock = &group->inotify_data.idr_lock;
- mask = inotify_arg_to_mask(arg);
+ mask = inotify_arg_to_mask(inode, arg);
tmp_i_mark = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL);
if (unlikely(!tmp_i_mark))