-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEq1nRK9aeMoq1VSgcnJ2qBz9kQNkFAl8qeCkACgkQnJ2qBz9k
 QNlAGQf/YVruyVLZ7kCv6EMCHauXm3K1lEGpbXsTW04HpStxGx7mtLGN/Au+EYJR
 VnRkCMt6TSMQGMBkNF83dUCwXHkeL1rd6frJBLVOErkg50nUuD4kjTVw9Lzw9itx
 CPhKnPPlsRkDkZPxkg3WEdqPgzJREWBZUaB38QUPjYN46q7HfPYDANTh5wI1GiGs
 27+PvzlttjhkQpQ14pYU/nu4xf/nmgmmHhgfsJArQP2EzYOrKxsWKhXS5uPdtNlf
 mXiZMaqW2AlyDGlw3myOEySrrSuaR77M2bzDo7mjqffI9wSVTytKEhtg0i8OMWmv
 pZ38OQobznnFoqzc1GL70IE0DEU48g==
 =d81d
 -----END PGP SIGNATURE-----

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
  ...
This commit is contained in:
Linus Torvalds 2020-08-06 19:29:51 -07:00
commit eb65405eb6
18 changed files with 967 additions and 425 deletions

View File

@ -883,6 +883,7 @@ repeat:
list_for_each_entry(info, &kernfs_root(kn)->supers, node) { list_for_each_entry(info, &kernfs_root(kn)->supers, node) {
struct kernfs_node *parent; struct kernfs_node *parent;
struct inode *p_inode = NULL;
struct inode *inode; struct inode *inode;
struct qstr name; struct qstr name;
@ -899,20 +900,20 @@ repeat:
name = (struct qstr)QSTR_INIT(kn->name, strlen(kn->name)); name = (struct qstr)QSTR_INIT(kn->name, strlen(kn->name));
parent = kernfs_get_parent(kn); parent = kernfs_get_parent(kn);
if (parent) { if (parent) {
struct inode *p_inode;
p_inode = ilookup(info->sb, kernfs_ino(parent)); p_inode = ilookup(info->sb, kernfs_ino(parent));
if (p_inode) { if (p_inode) {
fsnotify(p_inode, FS_MODIFY | FS_EVENT_ON_CHILD, fsnotify(FS_MODIFY | FS_EVENT_ON_CHILD,
inode, FSNOTIFY_EVENT_INODE, &name, 0); inode, FSNOTIFY_EVENT_INODE,
p_inode, &name, inode, 0);
iput(p_inode); iput(p_inode);
} }
kernfs_put(parent); kernfs_put(parent);
} }
fsnotify(inode, FS_MODIFY, inode, FSNOTIFY_EVENT_INODE, if (!p_inode)
&name, 0); fsnotify_inode(inode, FS_MODIFY);
iput(inode); iput(inode);
} }

View File

@ -598,11 +598,9 @@ static struct notifier_block nfsd_file_lease_notifier = {
}; };
static int static int
nfsd_file_fsnotify_handle_event(struct fsnotify_group *group, nfsd_file_fsnotify_handle_event(struct fsnotify_mark *mark, u32 mask,
struct inode *inode, struct inode *inode, struct inode *dir,
u32 mask, const void *data, int data_type, const struct qstr *name)
const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info)
{ {
trace_nfsd_file_fsnotify_handle_event(inode, mask); 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 = { 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, .free_mark = nfsd_file_mark_free,
}; };

View File

@ -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 * destroy the dnotify struct if it was not registered to receive multiple
* events. * events.
*/ */
static int dnotify_handle_event(struct fsnotify_group *group, static int dnotify_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
struct inode *inode, struct inode *inode, struct inode *dir,
u32 mask, const void *data, int data_type, const struct qstr *name)
const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info)
{ {
struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
struct dnotify_mark *dn_mark; struct dnotify_mark *dn_mark;
struct dnotify_struct *dn; struct dnotify_struct *dn;
struct dnotify_struct **prev; 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; __u32 test_mask = mask & ~FS_EVENT_ON_CHILD;
/* not a dir, dnotify doesn't care */ /* not a dir, dnotify doesn't care */
if (!S_ISDIR(inode->i_mode)) if (!dir && !(mask & FS_ISDIR))
return 0;
if (WARN_ON(fsnotify_iter_vfsmount_mark(iter_info)))
return 0; return 0;
dn_mark = container_of(inode_mark, struct dnotify_mark, fsn_mark); 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 = { static const struct fsnotify_ops dnotify_fsnotify_ops = {
.handle_event = dnotify_handle_event, .handle_inode_event = dnotify_handle_event,
.free_mark = dnotify_free_mark, .free_mark = dnotify_free_mark,
}; };

View File

@ -34,10 +34,6 @@ static bool fanotify_fh_equal(struct fanotify_fh *fh1,
if (fh1->type != fh2->type || fh1->len != fh2->len) if (fh1->type != fh2->type || fh1->len != fh2->len)
return false; return false;
/* Do not merge events if we failed to encode fh */
if (fh1->type == FILEID_INVALID)
return false;
return !fh1->len || return !fh1->len ||
!memcmp(fanotify_fh_buf(fh1), fanotify_fh_buf(fh2), 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); 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, static bool fanotify_name_event_equal(struct fanotify_name_event *fne1,
struct fanotify_name_event *fne2) struct fanotify_name_event *fne2)
{ {
/* struct fanotify_info *info1 = &fne1->info;
* Do not merge name events without dir fh. struct fanotify_info *info2 = &fne2->info;
* FAN_DIR_MODIFY does not encode object fh, so it may be empty.
*/ /* Do not merge name events without dir fh */
if (!fne1->dir_fh.len) if (!info1->dir_fh_totlen)
return false; return false;
if (fne1->name_len != fne2->name_len || if (!fanotify_fsid_equal(&fne1->fsid, &fne2->fsid))
!fanotify_fh_equal(&fne1->dir_fh, &fne2->dir_fh))
return false; 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, 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; 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) old->type != new->type || old->pid != new->pid)
return false; 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) { switch (old->type) {
case FANOTIFY_EVENT_TYPE_PATH: case FANOTIFY_EVENT_TYPE_PATH:
return fanotify_path_equal(fanotify_event_path(old), return fanotify_path_equal(fanotify_event_path(old),
fanotify_event_path(new)); fanotify_event_path(new));
case FANOTIFY_EVENT_TYPE_FID: 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), return fanotify_fid_event_equal(FANOTIFY_FE(old),
FANOTIFY_FE(new)); FANOTIFY_FE(new));
case FANOTIFY_EVENT_TYPE_FID_NAME: case FANOTIFY_EVENT_TYPE_FID_NAME:
@ -208,24 +226,30 @@ out:
static u32 fanotify_group_event_mask(struct fsnotify_group *group, static u32 fanotify_group_event_mask(struct fsnotify_group *group,
struct fsnotify_iter_info *iter_info, struct fsnotify_iter_info *iter_info,
u32 event_mask, const void *data, u32 event_mask, const void *data,
int data_type) int data_type, struct inode *dir)
{ {
__u32 marks_mask = 0, marks_ignored_mask = 0; __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); 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; struct fsnotify_mark *mark;
int type; int type;
pr_debug("%s: report_mask=%x mask=%x data=%p data_type=%d\n", pr_debug("%s: report_mask=%x mask=%x data=%p data_type=%d\n",
__func__, iter_info->report_mask, event_mask, data, data_type); __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? */ /* Do we have path to open a file descriptor? */
if (!path) if (!path)
return 0; return 0;
/* Path type events are only relevant for files and dirs */ /* Path type events are only relevant for files and dirs */
if (!d_is_reg(path->dentry) && !d_can_lookup(path->dentry)) if (!d_is_reg(path->dentry) && !d_can_lookup(path->dentry))
return 0; 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) { fsnotify_foreach_obj_type(type) {
@ -244,12 +268,12 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
continue; continue;
/* /*
* If the event is for a child and this mark doesn't care about * If the event is for a child and this mark is on a parent not
* events on a child, don't send it! * watching children, don't send it!
*/ */
if (event_mask & FS_EVENT_ON_CHILD && if (event_mask & FS_EVENT_ON_CHILD &&
(type != FSNOTIFY_OBJ_TYPE_INODE || type == FSNOTIFY_OBJ_TYPE_INODE &&
!(mark->mask & FS_EVENT_ON_CHILD))) !(mark->mask & FS_EVENT_ON_CHILD))
continue; continue;
marks_mask |= mark->mask; 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 * 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 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)) { if (fid_mode) {
/* Do not report FAN_ONDIR without any event */ /* Do not report event flags without any event */
if (!(test_mask & ~FAN_ONDIR)) if (!(test_mask & ~FANOTIFY_EVENT_FLAGS))
return 0; return 0;
} else { } else {
user_mask &= ~FAN_ONDIR; user_mask &= ~FANOTIFY_EVENT_FLAGS;
} }
return test_mask & user_mask; 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; char *ext_buf = NULL;
void *buf = fh->buf; void *buf = fh->buf;
int err; int err;
fh->type = FILEID_ROOT;
fh->len = 0;
fh->flags = 0;
if (!inode) 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; err = -ENOENT;
type = exportfs_encode_inode_fh(inode, NULL, &dwords, NULL); if (fh_len < 4 || WARN_ON_ONCE(fh_len % 4))
if (!dwords)
goto out_err; goto out_err;
bytes = dwords << 2; /* No external buffer in a variable size allocated fh */
if (bytes > FANOTIFY_INLINE_FH_LEN) { if (gfp && fh_len > FANOTIFY_INLINE_FH_LEN) {
/* Treat failure to allocate fh as failure to allocate event */ /* Treat failure to allocate fh as failure to encode fh */
err = -ENOMEM; err = -ENOMEM;
ext_buf = kmalloc(bytes, gfp); ext_buf = kmalloc(fh_len, gfp);
if (!ext_buf) if (!ext_buf)
goto out_err; goto out_err;
*fanotify_fh_ext_buf_ptr(fh) = ext_buf; *fanotify_fh_ext_buf_ptr(fh) = ext_buf;
buf = 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); type = exportfs_encode_inode_fh(inode, buf, &dwords, NULL);
err = -EINVAL; err = -EINVAL;
if (!type || type == FILEID_INVALID || bytes != dwords << 2) if (!type || type == FILEID_INVALID || fh_len != dwords << 2)
goto out_err; goto out_err;
fh->type = type; fh->type = type;
fh->len = bytes; fh->len = fh_len;
return; return FANOTIFY_FH_HDR_LEN + fh_len;
out_err: out_err:
pr_warn_ratelimited("fanotify: failed to encode fid (type=%d, len=%d, err=%i)\n", 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); kfree(ext_buf);
*fanotify_fh_ext_buf_ptr(fh) = NULL; *fanotify_fh_ext_buf_ptr(fh) = NULL;
out:
/* Report the event without a file identifier on encode error */ /* Report the event without a file identifier on encode error */
fh->type = FILEID_INVALID; fh->type = FILEID_INVALID;
fh->len = 0; fh->len = 0;
return 0;
} }
/* /*
@ -335,27 +394,179 @@ out:
* FS_ATTRIB reports the child inode even if reported on a watched parent. * 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. * 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, static struct inode *fanotify_fid_inode(u32 event_mask, const void *data,
const void *data, int data_type) int data_type, struct inode *dir)
{ {
if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS) if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS)
return to_tell; return dir;
return (struct inode *)fsnotify_data_inode(data, data_type); return fsnotify_data_inode(data, data_type);
} }
struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group, /*
struct inode *inode, u32 mask, * The inode to use as identifier when reporting dir fid depends on the event.
const void *data, int data_type, * Report the modified directory inode on dirent modification events.
const struct qstr *file_name, * Report the "victim" inode if "victim" is a directory.
__kernel_fsid_t *fsid) * 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;
}
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;
}
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_event *event = NULL;
struct fanotify_fid_event *ffe = NULL;
struct fanotify_name_event *fne = NULL;
gfp_t gfp = GFP_KERNEL_ACCOUNT; 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); 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 * 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); memalloc_use_memcg(group->memcg);
if (fanotify_is_perm_event(mask)) { if (fanotify_is_perm_event(mask)) {
struct fanotify_perm_event *pevent; event = fanotify_alloc_perm_event(path, gfp);
} else if (name_event && (file_name || child)) {
pevent = kmem_cache_alloc(fanotify_perm_event_cachep, gfp); event = fanotify_alloc_name_event(id, fsid, file_name, child,
if (!pevent) gfp);
goto out; } else if (fid_mode) {
event = fanotify_alloc_fid_event(id, fsid, gfp);
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;
} else { } else {
struct fanotify_path_event *pevent; event = fanotify_alloc_path_event(path, gfp);
pevent = kmem_cache_alloc(fanotify_path_event_cachep, gfp);
if (!pevent)
goto out;
event = &pevent->fae;
event->type = FANOTIFY_EVENT_TYPE_PATH;
} }
init: if (!event)
goto out;
/* /*
* Use the victim inode instead of the watching inode as the id for * Use the victim inode instead of the watching inode as the id for
* event queue, so event reported on parent is merged with event * event queue, so event reported on parent is merged with event
* reported on child when both directory and child watches exist. * reported on child when both directory and child watches exist.
*/ */
fsnotify_init_event(&event->fse, (unsigned long)id); fanotify_init_event(event, (unsigned long)id, mask);
event->mask = mask;
if (FAN_GROUP_FLAG(group, FAN_REPORT_TID)) if (FAN_GROUP_FLAG(group, FAN_REPORT_TID))
event->pid = get_pid(task_pid(current)); event->pid = get_pid(task_pid(current));
else else
event->pid = get_pid(task_tgid(current)); 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: out:
memalloc_unuse_memcg(); memalloc_unuse_memcg();
return event; return event;
@ -491,9 +645,9 @@ static __kernel_fsid_t fanotify_get_fsid(struct fsnotify_iter_info *iter_info)
return fsid; return fsid;
} }
static int fanotify_handle_event(struct fsnotify_group *group, static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
struct inode *inode, const void *data, int data_type,
u32 mask, const void *data, int data_type, struct inode *dir,
const struct qstr *file_name, u32 cookie, const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info) 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_MOVED_FROM != FS_MOVED_FROM);
BUILD_BUG_ON(FAN_CREATE != FS_CREATE); BUILD_BUG_ON(FAN_CREATE != FS_CREATE);
BUILD_BUG_ON(FAN_DELETE != FS_DELETE); 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_DELETE_SELF != FS_DELETE_SELF);
BUILD_BUG_ON(FAN_MOVE_SELF != FS_MOVE_SELF); BUILD_BUG_ON(FAN_MOVE_SELF != FS_MOVE_SELF);
BUILD_BUG_ON(FAN_EVENT_ON_CHILD != FS_EVENT_ON_CHILD); 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); BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 19);
mask = fanotify_group_event_mask(group, iter_info, mask, data, mask = fanotify_group_event_mask(group, iter_info, mask, data,
data_type); data_type, dir);
if (!mask) if (!mask)
return 0; return 0;
pr_debug("%s: group=%p inode=%p mask=%x\n", __func__, group, inode, pr_debug("%s: group=%p mask=%x\n", __func__, group, mask);
mask);
if (fanotify_is_perm_event(mask)) { if (fanotify_is_perm_event(mask)) {
/* /*
@ -542,14 +694,14 @@ static int fanotify_handle_event(struct fsnotify_group *group,
return 0; return 0;
} }
if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) { if (FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS)) {
fsid = fanotify_get_fsid(iter_info); fsid = fanotify_get_fsid(iter_info);
/* Racing with mark destruction or creation? */ /* Racing with mark destruction or creation? */
if (!fsid.val[0] && !fsid.val[1]) if (!fsid.val[0] && !fsid.val[1])
return 0; 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); file_name, &fsid);
ret = -ENOMEM; ret = -ENOMEM;
if (unlikely(!event)) { 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) static void fanotify_free_name_event(struct fanotify_event *event)
{ {
struct fanotify_name_event *fne = FANOTIFY_NE(event); kfree(FANOTIFY_NE(event));
if (fanotify_fh_has_ext_buf(&fne->dir_fh))
kfree(fanotify_fh_ext_buf(&fne->dir_fh));
kfree(fne);
} }
static void fanotify_free_event(struct fsnotify_event *fsn_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: case FANOTIFY_EVENT_TYPE_FID_NAME:
fanotify_free_name_event(event); fanotify_free_name_event(event);
break; break;
case FANOTIFY_EVENT_TYPE_OVERFLOW:
kfree(event);
break;
default: default:
WARN_ON_ONCE(1); WARN_ON_ONCE(1);
} }

View File

@ -23,20 +23,41 @@ enum {
* stored in either the first or last 2 dwords. * stored in either the first or last 2 dwords.
*/ */
#define FANOTIFY_INLINE_FH_LEN (3 << 2) #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 { struct fanotify_fh {
unsigned char buf[FANOTIFY_INLINE_FH_LEN];
u8 type; u8 type;
u8 len; 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); } __aligned(4);
static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh) 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) 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 *) > BUILD_BUG_ON(__alignof__(char *) - 4 + sizeof(char *) >
FANOTIFY_INLINE_FH_LEN); FANOTIFY_INLINE_FH_LEN);
return (char **)ALIGN((unsigned long)(fh->buf), __alignof__(char *)); 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; 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 * Common structure for fanotify events. Concrete structs are allocated in
* fanotify_handle_event() and freed when the information is retrieved by * 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_FID_NAME, /* variable length */
FANOTIFY_EVENT_TYPE_PATH, FANOTIFY_EVENT_TYPE_PATH,
FANOTIFY_EVENT_TYPE_PATH_PERM, FANOTIFY_EVENT_TYPE_PATH_PERM,
FANOTIFY_EVENT_TYPE_OVERFLOW, /* struct fanotify_event */
}; };
struct fanotify_event { struct fanotify_event {
@ -72,10 +144,20 @@ struct fanotify_event {
struct pid *pid; 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_fid_event {
struct fanotify_event fae; struct fanotify_event fae;
__kernel_fsid_t fsid; __kernel_fsid_t fsid;
struct fanotify_fh object_fh; 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 * static inline struct fanotify_fid_event *
@ -87,9 +169,7 @@ FANOTIFY_FE(struct fanotify_event *event)
struct fanotify_name_event { struct fanotify_name_event {
struct fanotify_event fae; struct fanotify_event fae;
__kernel_fsid_t fsid; __kernel_fsid_t fsid;
struct fanotify_fh dir_fh; struct fanotify_info info;
u8 name_len;
char name[];
}; };
static inline struct fanotify_name_event * 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) if (event->type == FANOTIFY_EVENT_TYPE_FID)
return &FANOTIFY_FE(event)->object_fh; 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 else
return NULL; return NULL;
} }
static inline struct fanotify_fh *fanotify_event_dir_fh( static inline struct fanotify_info *fanotify_event_info(
struct fanotify_event *event) struct fanotify_event *event)
{ {
if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME) if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME)
return &FANOTIFY_NE(event)->dir_fh; return &FANOTIFY_NE(event)->info;
else else
return NULL; return NULL;
} }
static inline int fanotify_event_object_fh_len(struct fanotify_event *event) 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); 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 info ? fanotify_info_dir_fh_len(info) : 0;
{
return fanotify_event_has_name(event) ?
FANOTIFY_NE(event)->name_len : 0;
} }
struct fanotify_path_event { struct fanotify_path_event {
@ -202,9 +284,3 @@ static inline struct path *fanotify_event_path(struct fanotify_event *event)
else else
return NULL; 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);

View File

@ -64,20 +64,27 @@ static int fanotify_fid_info_len(int fh_len, int name_len)
return roundup(FANOTIFY_INFO_HDR_LEN + info_len, FANOTIFY_EVENT_ALIGN); 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 fh_len = fanotify_event_object_fh_len(event);
int info_len = 0;
int dot_len = 0;
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) if (fh_len)
info_len += fanotify_fid_info_len(fh_len, 0); info_len += fanotify_fid_info_len(fh_len, dot_len);
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);
}
return info_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; size_t event_size = FAN_EVENT_METADATA_LEN;
struct fanotify_event *event = NULL; 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); 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)) if (fsnotify_notify_queue_is_empty(group))
goto out; goto out;
if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) { if (fid_mode) {
event_size += fanotify_event_info_len( event_size += fanotify_event_info_len(fid_mode,
FANOTIFY_E(fsnotify_peek_first_event(group))); 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, 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) char __user *buf, size_t count)
{ {
struct fanotify_event_info_fid info = { }; 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", pr_debug("%s: fh_len=%zu name_len=%zu, info_len=%zu, count=%zu\n",
__func__, fh_len, name_len, info_len, count); __func__, fh_len, name_len, info_len, count);
if (!fh_len || (name && !name_len)) if (!fh_len)
return 0; return 0;
if (WARN_ON_ONCE(len < sizeof(info) || len > count)) 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 * Copy event info fid header followed by variable sized file handle
* and optionally followed by variable sized filename. * and optionally followed by variable sized filename.
*/ */
info.hdr.info_type = name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME : switch (info_type) {
FAN_EVENT_INFO_TYPE_FID; 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.hdr.len = len;
info.fsid = *fsid; info.fsid = *fsid;
if (copy_to_user(buf, &info, sizeof(info))) 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 fanotify_event_metadata metadata;
struct path *path = fanotify_event_path(event); 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; struct file *f = NULL;
int ret, fd = FAN_NOFD; int ret, fd = FAN_NOFD;
int info_type = 0;
pr_debug("%s: group=%p event=%p\n", __func__, group, event); pr_debug("%s: group=%p event=%p\n", __func__, group, event);
metadata.event_len = FAN_EVENT_METADATA_LEN + 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.metadata_len = FAN_EVENT_METADATA_LEN;
metadata.vers = FANOTIFY_METADATA_VERSION; metadata.vers = FANOTIFY_METADATA_VERSION;
metadata.reserved = 0; metadata.reserved = 0;
@ -346,13 +370,13 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
fd_install(fd, f); fd_install(fd, f);
/* Event info records order is: dir fid + name, child fid */ /* Event info records order is: dir fid + name, child fid */
if (fanotify_event_name_len(event)) { if (fanotify_event_dir_fh_len(event)) {
struct fanotify_name_event *fne = FANOTIFY_NE(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), ret = copy_info_to_user(fanotify_event_fsid(event),
fanotify_event_dir_fh(event), fanotify_info_dir_fh(info),
fne->name, fne->name_len, info_type, fanotify_info_name(info),
buf, count); info->name_len, buf, count);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -361,9 +385,46 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
} }
if (fanotify_event_object_fh_len(event)) { 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), ret = copy_info_to_user(fanotify_event_fsid(event),
fanotify_event_object_fh(event), fanotify_event_object_fh(event),
NULL, 0, buf, count); info_type, dot, dot_len, buf, count);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -412,6 +473,11 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
add_wait_queue(&group->notification_waitq, &wait); add_wait_queue(&group->notification_waitq, &wait);
while (1) { 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); event = get_one_event(group, count);
if (IS_ERR(event)) { if (IS_ERR(event)) {
ret = PTR_ERR(event); ret = PTR_ERR(event);
@ -651,12 +717,13 @@ out:
} }
static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark, static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
__u32 mask, __u32 mask, unsigned int flags,
unsigned int flags, __u32 umask, int *destroy)
int *destroy)
{ {
__u32 oldmask = 0; __u32 oldmask = 0;
/* umask bits cannot be removed by user */
mask &= ~umask;
spin_lock(&fsn_mark->lock); spin_lock(&fsn_mark->lock);
if (!(flags & FAN_MARK_IGNORED_MASK)) { if (!(flags & FAN_MARK_IGNORED_MASK)) {
oldmask = fsn_mark->mask; oldmask = fsn_mark->mask;
@ -664,7 +731,13 @@ static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark,
} else { } else {
fsn_mark->ignored_mask &= ~mask; 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); spin_unlock(&fsn_mark->lock);
return mask & oldmask; 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, static int fanotify_remove_mark(struct fsnotify_group *group,
fsnotify_connp_t *connp, __u32 mask, fsnotify_connp_t *connp, __u32 mask,
unsigned int flags) unsigned int flags, __u32 umask)
{ {
struct fsnotify_mark *fsn_mark = NULL; struct fsnotify_mark *fsn_mark = NULL;
__u32 removed; __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, removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags,
&destroy_mark); umask, &destroy_mark);
if (removed & fsnotify_conn_mask(fsn_mark->connector)) if (removed & fsnotify_conn_mask(fsn_mark->connector))
fsnotify_recalc_mask(fsn_mark->connector); fsnotify_recalc_mask(fsn_mark->connector);
if (destroy_mark) 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, static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group,
struct vfsmount *mnt, __u32 mask, struct vfsmount *mnt, __u32 mask,
unsigned int flags) unsigned int flags, __u32 umask)
{ {
return fanotify_remove_mark(group, &real_mount(mnt)->mnt_fsnotify_marks, 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, static int fanotify_remove_sb_mark(struct fsnotify_group *group,
struct super_block *sb, __u32 mask, struct super_block *sb, __u32 mask,
unsigned int flags) 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, static int fanotify_remove_inode_mark(struct fsnotify_group *group,
struct inode *inode, __u32 mask, struct inode *inode, __u32 mask,
unsigned int flags) unsigned int flags, __u32 umask)
{ {
return fanotify_remove_mark(group, &inode->i_fsnotify_marks, mask, 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, 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); 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 */ /* fanotify syscalls */
SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags) SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
{ {
struct fsnotify_group *group; struct fsnotify_group *group;
int f_flags, fd; int f_flags, fd;
struct user_struct *user; 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", pr_debug("%s: flags=%x event_f_flags=%x\n",
__func__, flags, event_f_flags); __func__, flags, event_f_flags);
@ -864,8 +953,14 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
return -EINVAL; return -EINVAL;
} }
if ((flags & FAN_REPORT_FID) && if (fid_mode && class != FAN_CLASS_NOTIF)
(flags & FANOTIFY_CLASS_BITS) != 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; return -EINVAL;
user = get_current_user(); 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); atomic_inc(&user->fanotify_listeners);
group->memcg = get_mem_cgroup_from_mm(current->mm); group->memcg = get_mem_cgroup_from_mm(current->mm);
oevent = fanotify_alloc_event(group, NULL, FS_Q_OVERFLOW, NULL, group->overflow_event = fanotify_alloc_overflow_event();
FSNOTIFY_EVENT_NONE, NULL, NULL); if (unlikely(!group->overflow_event)) {
if (unlikely(!oevent)) {
fd = -ENOMEM; fd = -ENOMEM;
goto out_destroy_group; goto out_destroy_group;
} }
group->overflow_event = &oevent->fse;
if (force_o_largefile()) if (force_o_largefile())
event_f_flags |= O_LARGEFILE; event_f_flags |= O_LARGEFILE;
group->fanotify_data.f_flags = event_f_flags; group->fanotify_data.f_flags = event_f_flags;
init_waitqueue_head(&group->fanotify_data.access_waitq); init_waitqueue_head(&group->fanotify_data.access_waitq);
INIT_LIST_HEAD(&group->fanotify_data.access_list); INIT_LIST_HEAD(&group->fanotify_data.access_list);
switch (flags & FANOTIFY_CLASS_BITS) { switch (class) {
case FAN_CLASS_NOTIF: case FAN_CLASS_NOTIF:
group->priority = FS_PRIO_0; group->priority = FS_PRIO_0;
break; break;
@ -1024,7 +1117,9 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
__kernel_fsid_t __fsid, *fsid = NULL; __kernel_fsid_t __fsid, *fsid = NULL;
u32 valid_mask = FANOTIFY_EVENTS | FANOTIFY_EVENT_FLAGS; u32 valid_mask = FANOTIFY_EVENTS | FANOTIFY_EVENT_FLAGS;
unsigned int mark_type = flags & FANOTIFY_MARK_TYPE_BITS; 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; int ret;
pr_debug("%s: fanotify_fd=%d flags=%x dfd=%d pathname=%p mask=%llx\n", 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) if (mask & ~valid_mask)
return -EINVAL; return -EINVAL;
/* Event flags (ONDIR, ON_CHILD) are meaningless in ignored mask */
if (ignored)
mask &= ~FANOTIFY_EVENT_FLAGS;
f = fdget(fanotify_fd); f = fdget(fanotify_fd);
if (unlikely(!f.file)) if (unlikely(!f.file))
return -EBADF; 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 * 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. * 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 && if (mask & FANOTIFY_INODE_EVENTS &&
(!FAN_GROUP_FLAG(group, FAN_REPORT_FID) || (!fid_mode || mark_type == FAN_MARK_MOUNT))
mark_type == FAN_MARK_MOUNT))
goto fput_and_out; goto fput_and_out;
if (flags & FAN_MARK_FLUSH) { 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; goto path_put_and_out;
} }
if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) { if (fid_mode) {
ret = fanotify_test_fid(&path, &__fsid); ret = fanotify_test_fid(&path, &__fsid);
if (ret) if (ret)
goto path_put_and_out; goto path_put_and_out;
@ -1138,6 +1237,19 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
else else
mnt = path.mnt; 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 */ /* create/update an inode mark */
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) { switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
case FAN_MARK_ADD: 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: case FAN_MARK_REMOVE:
if (mark_type == FAN_MARK_MOUNT) if (mark_type == FAN_MARK_MOUNT)
ret = fanotify_remove_vfsmount_mark(group, mnt, mask, ret = fanotify_remove_vfsmount_mark(group, mnt, mask,
flags); flags, umask);
else if (mark_type == FAN_MARK_FILESYSTEM) else if (mark_type == FAN_MARK_FILESYSTEM)
ret = fanotify_remove_sb_mark(group, mnt->mnt_sb, mask, ret = fanotify_remove_sb_mark(group, mnt->mnt_sb, mask,
flags); flags, umask);
else else
ret = fanotify_remove_inode_mark(group, inode, mask, ret = fanotify_remove_inode_mark(group, inode, mask,
flags); flags, umask);
break; break;
default: default:
ret = -EINVAL; ret = -EINVAL;
@ -1203,7 +1315,7 @@ COMPAT_SYSCALL_DEFINE6(fanotify_mark,
*/ */
static int __init fanotify_user_setup(void) 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); BUILD_BUG_ON(HWEIGHT32(FANOTIFY_MARK_FLAGS) != 9);
fanotify_mark_cache = KMEM_CACHE(fsnotify_mark, fanotify_mark_cache = KMEM_CACHE(fsnotify_mark,

View File

@ -74,7 +74,7 @@ static void fsnotify_unmount_inodes(struct super_block *sb)
iput(iput_inode); iput(iput_inode);
/* for each watch, send FS_UNMOUNT and then remove it */ /* 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); fsnotify_inode_delete(inode);
@ -142,45 +142,140 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
spin_unlock(&inode->i_lock); spin_unlock(&inode->i_lock);
} }
/* Notify this dentry's parent about a child's events. */ /* Are inode/sb/mount interested in parent and name info with this event? */
int fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data, static bool fsnotify_event_needs_parent(struct inode *inode, struct mount *mnt,
int data_type) __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 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; 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; 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); 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) {
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. */ * Include parent/name in notification either if some notification
mask |= FS_EVENT_ON_CHILD; * 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); take_dentry_name_snapshot(&name, dentry);
ret = fsnotify(p_inode, mask, data, data_type, &name.name, 0); file_name = &name.name;
release_dentry_name_snapshot(&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); dput(parent);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(fsnotify_parent); EXPORT_SYMBOL_GPL(__fsnotify_parent);
static int send_to_group(struct inode *to_tell, static int fsnotify_handle_event(struct fsnotify_group *group, __u32 mask,
__u32 mask, const void *data, const void *data, int data_type,
int data_is, u32 cookie, struct inode *dir, const struct qstr *name,
const struct qstr *file_name, u32 cookie, struct fsnotify_iter_info *iter_info)
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(__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; struct fsnotify_group *group = NULL;
__u32 test_mask = (mask & ALL_FSNOTIFY_EVENTS); __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" pr_debug("%s: group=%p mask=%x marks_mask=%x marks_ignored_mask=%x data=%p data_type=%d dir=%p cookie=%d\n",
" data=%p data_is=%d cookie=%d\n", __func__, group, mask, marks_mask, marks_ignored_mask,
__func__, group, to_tell, mask, marks_mask, marks_ignored_mask, data, data_type, dir, cookie);
data, data_is, cookie);
if (!(test_mask & marks_mask & ~marks_ignored_mask)) if (!(test_mask & marks_mask & ~marks_ignored_mask))
return 0; return 0;
return group->ops->handle_event(group, to_tell, mask, data, data_is, if (group->ops->handle_event) {
file_name, cookie, iter_info); 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) 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 * fsnotify - This is the main call to fsnotify.
* 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 * The VFS calls into hook specific functions in linux/fsnotify.h.
* notification event in whatever means they feel necessary. * 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, int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
const struct qstr *file_name, u32 cookie) 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 fsnotify_iter_info iter_info = {};
struct super_block *sb = to_tell->i_sb; struct super_block *sb;
struct mount *mnt = NULL; struct mount *mnt = NULL;
__u32 mnt_or_sb_mask = sb->s_fsnotify_mask; struct inode *child = NULL;
int ret = 0; 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 = 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 */ sb = inode->i_sb;
if (mask & FS_EVENT_ON_CHILD)
mnt_or_sb_mask = 0;
/* /*
* Optimization: srcu_read_lock() has a memory barrier which can * 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 * 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 (!to_tell->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))
return 0; 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 * 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 * otherwise return if none of the marks care about this type of event.
* this type of event.
*/ */
if (!(mask & FS_MODIFY) && test_mask = (mask & ALL_FSNOTIFY_EVENTS);
!(test_mask & (to_tell->i_fsnotify_mask | mnt_or_sb_mask))) if (!(mask & FS_MODIFY) && !(test_mask & marks_mask))
return 0; return 0;
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(&to_tell->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) {
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 * 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... * That's why this traversal is so complicated...
*/ */
while (fsnotify_iter_select_report_types(&iter_info)) { while (fsnotify_iter_select_report_types(&iter_info)) {
ret = send_to_group(to_tell, mask, data, data_is, cookie, ret = send_to_group(mask, data, data_type, dir, file_name,
file_name, &iter_info); cookie, &iter_info);
if (ret && (mask & ALL_FSNOTIFY_PERM_EVENTS)) if (ret && (mask & ALL_FSNOTIFY_PERM_EVENTS))
goto out; goto out;
@ -383,7 +521,7 @@ static __init int fsnotify_init(void)
{ {
int ret; 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); ret = init_srcu_struct(&fsnotify_mark_srcu);
if (ret) if (ret)

View File

@ -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, extern void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
struct fsnotify_group *group); struct fsnotify_group *group);
extern int inotify_handle_event(struct fsnotify_group *group, extern int inotify_handle_event(struct fsnotify_group *group, u32 mask,
struct inode *inode, const void *data, int data_type,
u32 mask, const void *data, int data_type, struct inode *dir,
const struct qstr *file_name, u32 cookie, const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info); struct fsnotify_iter_info *iter_info);

View File

@ -39,7 +39,7 @@ static bool event_compare(struct fsnotify_event *old_fsn,
if (old->mask & FS_IN_IGNORED) if (old->mask & FS_IN_IGNORED)
return false; return false;
if ((old->mask == new->mask) && if ((old->mask == new->mask) &&
(old_fsn->objectid == new_fsn->objectid) && (old->wd == new->wd) &&
(old->name_len == new->name_len) && (old->name_len == new->name_len) &&
(!old->name_len || !strcmp(old->name, new->name))) (!old->name_len || !strcmp(old->name, new->name)))
return true; return true;
@ -55,14 +55,11 @@ static int inotify_merge(struct list_head *list,
return event_compare(last_event, event); return event_compare(last_event, event);
} }
int inotify_handle_event(struct fsnotify_group *group, static int inotify_one_event(struct fsnotify_group *group, u32 mask,
struct inode *inode, struct fsnotify_mark *inode_mark,
u32 mask, const void *data, int data_type, const struct path *path,
const struct qstr *file_name, u32 cookie, 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 inotify_inode_mark *i_mark; struct inotify_inode_mark *i_mark;
struct inotify_event_info *event; struct inotify_event_info *event;
struct fsnotify_event *fsn_event; struct fsnotify_event *fsn_event;
@ -70,9 +67,6 @@ int inotify_handle_event(struct fsnotify_group *group,
int len = 0; int len = 0;
int alloc_len = sizeof(struct inotify_event_info); 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) && if ((inode_mark->mask & FS_EXCL_UNLINK) &&
path && d_unlinked(path->dentry)) path && d_unlinked(path->dentry))
return 0; return 0;
@ -82,7 +76,7 @@ int inotify_handle_event(struct fsnotify_group *group,
alloc_len += len + 1; 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); mask);
i_mark = container_of(inode_mark, struct inotify_inode_mark, 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; mask &= ~IN_ISDIR;
fsn_event = &event->fse; fsn_event = &event->fse;
fsnotify_init_event(fsn_event, (unsigned long)inode); fsnotify_init_event(fsn_event, 0);
event->mask = mask; event->mask = mask;
event->wd = i_mark->wd; event->wd = i_mark->wd;
event->sync_cookie = cookie; event->sync_cookie = cookie;
@ -136,6 +130,37 @@ int inotify_handle_event(struct fsnotify_group *group,
return 0; 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) static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group)
{ {
inotify_ignored_and_remove_idr(fsn_mark, group); inotify_ignored_and_remove_idr(fsn_mark, group);

View File

@ -75,15 +75,17 @@ struct ctl_table inotify_table[] = {
}; };
#endif /* CONFIG_SYSCTL */ #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; __u32 mask;
/* /*
* everything should accept their own ignored, cares about children, * Everything should accept their own ignored and should receive events
* and should receive events when the inode is unmounted * 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 off the flags used to open the fd */
mask |= (arg & (IN_ALL_EVENTS | IN_ONESHOT | IN_EXCL_UNLINK)); 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); fsn_mark);
/* Queue ignore event for the watch */ /* Queue ignore event for the watch */
inotify_handle_event(group, NULL, FS_IN_IGNORED, NULL, inotify_handle_event(group, FS_IN_IGNORED, NULL, FSNOTIFY_EVENT_NONE,
FSNOTIFY_EVENT_NONE, NULL, 0, &iter_info); NULL, NULL, 0, &iter_info);
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark); i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
/* remove this mark from the idr */ /* 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 create = (arg & IN_MASK_CREATE);
int ret; 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); fsn_mark = fsnotify_find_mark(&inode->i_fsnotify_marks, group);
if (!fsn_mark) if (!fsn_mark)
@ -565,7 +567,7 @@ static int inotify_new_watch(struct fsnotify_group *group,
struct idr *idr = &group->inotify_data.idr; struct idr *idr = &group->inotify_data.idr;
spinlock_t *idr_lock = &group->inotify_data.idr_lock; 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); tmp_i_mark = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL);
if (unlikely(!tmp_i_mark)) if (unlikely(!tmp_i_mark))

View File

@ -18,8 +18,10 @@
#define FANOTIFY_CLASS_BITS (FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \ #define FANOTIFY_CLASS_BITS (FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \
FAN_CLASS_PRE_CONTENT) FAN_CLASS_PRE_CONTENT)
#define FANOTIFY_INIT_FLAGS (FANOTIFY_CLASS_BITS | \ #define FANOTIFY_FID_BITS (FAN_REPORT_FID | FAN_REPORT_DFID_NAME)
FAN_REPORT_TID | FAN_REPORT_FID | \
#define FANOTIFY_INIT_FLAGS (FANOTIFY_CLASS_BITS | FANOTIFY_FID_BITS | \
FAN_REPORT_TID | \
FAN_CLOEXEC | FAN_NONBLOCK | \ FAN_CLOEXEC | FAN_NONBLOCK | \
FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS) FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS)

View File

@ -23,19 +23,14 @@
* have changed (i.e. renamed over). * have changed (i.e. renamed over).
* *
* Unlike fsnotify_parent(), the event will be reported regardless of the * Unlike fsnotify_parent(), the event will be reported regardless of the
* FS_EVENT_ON_CHILD mask on the parent inode. * FS_EVENT_ON_CHILD mask on the parent inode and will not be reported if only
* the child is interested and not the parent.
*/ */
static inline void fsnotify_name(struct inode *dir, __u32 mask, static inline void fsnotify_name(struct inode *dir, __u32 mask,
struct inode *child, struct inode *child,
const struct qstr *name, u32 cookie) const struct qstr *name, u32 cookie)
{ {
fsnotify(dir, mask, child, FSNOTIFY_EVENT_INODE, name, cookie); fsnotify(mask, child, FSNOTIFY_EVENT_INODE, dir, name, NULL, cookie);
/*
* Send another flavor of the event without child inode data and
* without the specific event type (e.g. FS_CREATE|FS_IS_DIR).
* The name is relative to the dir inode the event is reported to.
*/
fsnotify(dir, FS_DIR_MODIFY, dir, FSNOTIFY_EVENT_INODE, name, 0);
} }
static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry, static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry,
@ -44,38 +39,55 @@ static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry,
fsnotify_name(dir, mask, d_inode(dentry), &dentry->d_name, 0); fsnotify_name(dir, mask, d_inode(dentry), &dentry->d_name, 0);
} }
/* static inline void fsnotify_inode(struct inode *inode, __u32 mask)
* Simple wrappers to consolidate calls fsnotify_parent()/fsnotify() when
* an event is on a file/dentry.
*/
static inline void fsnotify_dentry(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; mask |= FS_ISDIR;
fsnotify_parent(dentry, mask, inode, FSNOTIFY_EVENT_INODE); fsnotify(mask, inode, FSNOTIFY_EVENT_INODE, NULL, NULL, inode, 0);
fsnotify(inode, mask, inode, FSNOTIFY_EVENT_INODE, NULL, 0); }
/* Notify this dentry's parent about a child's events. */
static inline int fsnotify_parent(struct dentry *dentry, __u32 mask,
const void *data, int data_type)
{
struct inode *inode = d_inode(dentry);
if (S_ISDIR(inode->i_mode)) {
mask |= FS_ISDIR;
/* 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);
notify_child:
return fsnotify(mask, data, data_type, NULL, NULL, inode, 0);
}
/*
* Simple wrappers to consolidate calls to fsnotify_parent() when an event
* is on a file/dentry.
*/
static inline void fsnotify_dentry(struct dentry *dentry, __u32 mask)
{
fsnotify_parent(dentry, mask, d_inode(dentry), FSNOTIFY_EVENT_INODE);
} }
static inline int fsnotify_file(struct file *file, __u32 mask) static inline int fsnotify_file(struct file *file, __u32 mask)
{ {
const struct path *path = &file->f_path; const struct path *path = &file->f_path;
struct inode *inode = file_inode(file);
int ret;
if (file->f_mode & FMODE_NONOTIFY) if (file->f_mode & FMODE_NONOTIFY)
return 0; return 0;
if (S_ISDIR(inode->i_mode)) return fsnotify_parent(path->dentry, mask, path, FSNOTIFY_EVENT_PATH);
mask |= FS_ISDIR;
ret = fsnotify_parent(path->dentry, mask, path, FSNOTIFY_EVENT_PATH);
if (ret)
return ret;
return fsnotify(inode, mask, path, FSNOTIFY_EVENT_PATH, NULL, 0);
} }
/* Simple call site for access decisions */ /* Simple call site for access decisions */
@ -108,12 +120,7 @@ static inline int fsnotify_perm(struct file *file, int mask)
*/ */
static inline void fsnotify_link_count(struct inode *inode) static inline void fsnotify_link_count(struct inode *inode)
{ {
__u32 mask = FS_ATTRIB; fsnotify_inode(inode, FS_ATTRIB);
if (S_ISDIR(inode->i_mode))
mask |= FS_ISDIR;
fsnotify(inode, mask, inode, FSNOTIFY_EVENT_INODE, NULL, 0);
} }
/* /*
@ -128,7 +135,6 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
u32 fs_cookie = fsnotify_get_cookie(); u32 fs_cookie = fsnotify_get_cookie();
__u32 old_dir_mask = FS_MOVED_FROM; __u32 old_dir_mask = FS_MOVED_FROM;
__u32 new_dir_mask = FS_MOVED_TO; __u32 new_dir_mask = FS_MOVED_TO;
__u32 mask = FS_MOVE_SELF;
const struct qstr *new_name = &moved->d_name; const struct qstr *new_name = &moved->d_name;
if (old_dir == new_dir) if (old_dir == new_dir)
@ -137,7 +143,6 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
if (isdir) { if (isdir) {
old_dir_mask |= FS_ISDIR; old_dir_mask |= FS_ISDIR;
new_dir_mask |= FS_ISDIR; new_dir_mask |= FS_ISDIR;
mask |= FS_ISDIR;
} }
fsnotify_name(old_dir, old_dir_mask, source, old_name, fs_cookie); fsnotify_name(old_dir, old_dir_mask, source, old_name, fs_cookie);
@ -145,9 +150,7 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
if (target) if (target)
fsnotify_link_count(target); fsnotify_link_count(target);
fsnotify_inode(source, FS_MOVE_SELF);
if (source)
fsnotify(source, mask, source, FSNOTIFY_EVENT_INODE, NULL, 0);
audit_inode_child(new_dir, moved, AUDIT_TYPE_CHILD_CREATE); audit_inode_child(new_dir, moved, AUDIT_TYPE_CHILD_CREATE);
} }
@ -172,12 +175,7 @@ static inline void fsnotify_vfsmount_delete(struct vfsmount *mnt)
*/ */
static inline void fsnotify_inoderemove(struct inode *inode) static inline void fsnotify_inoderemove(struct inode *inode)
{ {
__u32 mask = FS_DELETE_SELF; fsnotify_inode(inode, FS_DELETE_SELF);
if (S_ISDIR(inode->i_mode))
mask |= FS_ISDIR;
fsnotify(inode, mask, inode, FSNOTIFY_EVENT_INODE, NULL, 0);
__fsnotify_inode_delete(inode); __fsnotify_inode_delete(inode);
} }

View File

@ -47,11 +47,13 @@
#define FS_OPEN_PERM 0x00010000 /* open event in an permission hook */ #define FS_OPEN_PERM 0x00010000 /* open event in an permission hook */
#define FS_ACCESS_PERM 0x00020000 /* access event in a permissions hook */ #define FS_ACCESS_PERM 0x00020000 /* access event in a permissions hook */
#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_DIR_MODIFY 0x00080000 /* Directory entry was modified */
#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 */
@ -67,21 +69,28 @@
* The watching parent may get an FS_ATTRIB|FS_EVENT_ON_CHILD event * The watching parent may get an FS_ATTRIB|FS_EVENT_ON_CHILD event
* when a directory entry inside a child subdir changes. * when a directory entry inside a child subdir changes.
*/ */
#define ALL_FSNOTIFY_DIRENT_EVENTS (FS_CREATE | FS_DELETE | FS_MOVE | \ #define ALL_FSNOTIFY_DIRENT_EVENTS (FS_CREATE | FS_DELETE | FS_MOVE)
FS_DIR_MODIFY)
#define ALL_FSNOTIFY_PERM_EVENTS (FS_OPEN_PERM | FS_ACCESS_PERM | \ #define ALL_FSNOTIFY_PERM_EVENTS (FS_OPEN_PERM | FS_ACCESS_PERM | \
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 | \
@ -108,18 +117,41 @@ struct mem_cgroup;
* these operations for each relevant group. * these operations for each relevant group.
* *
* handle_event - main call for a group to handle an fs event * handle_event - main call for a group to handle an fs event
* @group: group to notify
* @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
* @cookie: inotify rename cookie
* @iter_info: array of marks from this group that are interested in the event
*
* handle_inode_event - simple variant of handle_event() for groups that only
* have inode marks and don't have ignore mask
* @mark: mark to notify
* @mask: event type and flags
* @inode: inode that event happened on
* @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
*
* free_group_priv - called when a group refcnt hits 0 to clean up the private union * free_group_priv - called when a group refcnt hits 0 to clean up the private union
* freeing_mark - called when a mark is being destroyed for some reason. The group * freeing_mark - called when a mark is being destroyed for some reason. The group
* MUST be holding a reference on each mark and that reference must be * MUST be holding a reference on each mark and that reference must be
* dropped in this function. inotify uses this function to send * dropped in this function. inotify uses this function to send
* userspace messages that marks have been removed. * userspace messages that marks have been removed.
*/ */
struct fsnotify_ops { struct fsnotify_ops {
int (*handle_event)(struct fsnotify_group *group, int (*handle_event)(struct fsnotify_group *group, u32 mask,
struct inode *inode, const void *data, int data_type, struct inode *dir,
u32 mask, const void *data, int data_type,
const struct qstr *file_name, u32 cookie, const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info); struct fsnotify_iter_info *iter_info);
int (*handle_inode_event)(struct fsnotify_mark *mark, u32 mask,
struct inode *inode, struct inode *dir,
const struct qstr *file_name);
void (*free_group_priv)(struct fsnotify_group *group); void (*free_group_priv)(struct fsnotify_group *group);
void (*freeing_mark)(struct fsnotify_mark *mark, struct fsnotify_group *group); void (*freeing_mark)(struct fsnotify_mark *mark, struct fsnotify_group *group);
void (*free_event)(struct fsnotify_event *event); void (*free_event)(struct fsnotify_event *event);
@ -220,12 +252,11 @@ enum fsnotify_data_type {
FSNOTIFY_EVENT_INODE, FSNOTIFY_EVENT_INODE,
}; };
static inline const struct inode *fsnotify_data_inode(const void *data, static inline struct inode *fsnotify_data_inode(const void *data, int data_type)
int data_type)
{ {
switch (data_type) { switch (data_type) {
case FSNOTIFY_EVENT_INODE: case FSNOTIFY_EVENT_INODE:
return data; return (struct inode *)data;
case FSNOTIFY_EVENT_PATH: case FSNOTIFY_EVENT_PATH:
return d_inode(((const struct path *)data)->dentry); return d_inode(((const struct path *)data)->dentry);
default: default:
@ -246,6 +277,7 @@ static inline const struct path *fsnotify_data_path(const void *data,
enum fsnotify_obj_type { enum fsnotify_obj_type {
FSNOTIFY_OBJ_TYPE_INODE, FSNOTIFY_OBJ_TYPE_INODE,
FSNOTIFY_OBJ_TYPE_CHILD,
FSNOTIFY_OBJ_TYPE_VFSMOUNT, FSNOTIFY_OBJ_TYPE_VFSMOUNT,
FSNOTIFY_OBJ_TYPE_SB, FSNOTIFY_OBJ_TYPE_SB,
FSNOTIFY_OBJ_TYPE_COUNT, FSNOTIFY_OBJ_TYPE_COUNT,
@ -253,6 +285,7 @@ enum fsnotify_obj_type {
}; };
#define FSNOTIFY_OBJ_TYPE_INODE_FL (1U << FSNOTIFY_OBJ_TYPE_INODE) #define FSNOTIFY_OBJ_TYPE_INODE_FL (1U << FSNOTIFY_OBJ_TYPE_INODE)
#define FSNOTIFY_OBJ_TYPE_CHILD_FL (1U << FSNOTIFY_OBJ_TYPE_CHILD)
#define FSNOTIFY_OBJ_TYPE_VFSMOUNT_FL (1U << FSNOTIFY_OBJ_TYPE_VFSMOUNT) #define FSNOTIFY_OBJ_TYPE_VFSMOUNT_FL (1U << FSNOTIFY_OBJ_TYPE_VFSMOUNT)
#define FSNOTIFY_OBJ_TYPE_SB_FL (1U << FSNOTIFY_OBJ_TYPE_SB) #define FSNOTIFY_OBJ_TYPE_SB_FL (1U << FSNOTIFY_OBJ_TYPE_SB)
#define FSNOTIFY_OBJ_ALL_TYPES_MASK ((1U << FSNOTIFY_OBJ_TYPE_COUNT) - 1) #define FSNOTIFY_OBJ_ALL_TYPES_MASK ((1U << FSNOTIFY_OBJ_TYPE_COUNT) - 1)
@ -297,6 +330,7 @@ static inline struct fsnotify_mark *fsnotify_iter_##name##_mark( \
} }
FSNOTIFY_ITER_FUNCS(inode, INODE) FSNOTIFY_ITER_FUNCS(inode, INODE)
FSNOTIFY_ITER_FUNCS(child, CHILD)
FSNOTIFY_ITER_FUNCS(vfsmount, VFSMOUNT) FSNOTIFY_ITER_FUNCS(vfsmount, VFSMOUNT)
FSNOTIFY_ITER_FUNCS(sb, SB) FSNOTIFY_ITER_FUNCS(sb, SB)
@ -377,15 +411,29 @@ struct fsnotify_mark {
/* called from the vfs helpers */ /* called from the vfs helpers */
/* main fsnotify call to send events */ /* main fsnotify call to send events */
extern int fsnotify(struct inode *to_tell, __u32 mask, const void *data, extern int fsnotify(__u32 mask, const void *data, int data_type,
int data_type, const struct qstr *name, u32 cookie); struct inode *dir, const struct qstr *name,
extern int fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data, struct inode *inode, u32 cookie);
extern int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
int data_type); int data_type);
extern void __fsnotify_inode_delete(struct inode *inode); extern void __fsnotify_inode_delete(struct inode *inode);
extern void __fsnotify_vfsmount_delete(struct vfsmount *mnt); 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 */
@ -535,13 +583,14 @@ static inline void fsnotify_init_event(struct fsnotify_event *event,
#else #else
static inline int fsnotify(struct inode *to_tell, __u32 mask, const void *data, static inline int fsnotify(__u32 mask, const void *data, int data_type,
int data_type, const struct qstr *name, u32 cookie) struct inode *dir, const struct qstr *name,
struct inode *inode, u32 cookie)
{ {
return 0; return 0;
} }
static inline int fsnotify_parent(struct dentry *dentry, __u32 mask, static inline int __fsnotify_parent(struct dentry *dentry, __u32 mask,
const void *data, int data_type) const void *data, int data_type)
{ {
return 0; return 0;

View File

@ -24,7 +24,6 @@
#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */ #define FAN_OPEN_PERM 0x00010000 /* File open in perm check */
#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ #define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */
#define FAN_OPEN_EXEC_PERM 0x00040000 /* File open/exec in perm check */ #define FAN_OPEN_EXEC_PERM 0x00040000 /* File open/exec in perm check */
#define FAN_DIR_MODIFY 0x00080000 /* Directory entry was modified */
#define FAN_EVENT_ON_CHILD 0x08000000 /* Interested in child events */ #define FAN_EVENT_ON_CHILD 0x08000000 /* Interested in child events */
@ -54,6 +53,11 @@
/* Flags to determine fanotify event format */ /* Flags to determine fanotify event format */
#define FAN_REPORT_TID 0x00000100 /* event->pid is thread id */ #define FAN_REPORT_TID 0x00000100 /* event->pid is thread id */
#define FAN_REPORT_FID 0x00000200 /* Report unique file id */ #define FAN_REPORT_FID 0x00000200 /* Report unique file id */
#define FAN_REPORT_DIR_FID 0x00000400 /* Report unique directory id */
#define FAN_REPORT_NAME 0x00000800 /* Report events with name */
/* Convenience macro - FAN_REPORT_NAME requires FAN_REPORT_DIR_FID */
#define FAN_REPORT_DFID_NAME (FAN_REPORT_DIR_FID | FAN_REPORT_NAME)
/* Deprecated - do not use this in programs and do not add new flags here! */ /* Deprecated - do not use this in programs and do not add new flags here! */
#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \ #define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \
@ -118,6 +122,7 @@ struct fanotify_event_metadata {
#define FAN_EVENT_INFO_TYPE_FID 1 #define FAN_EVENT_INFO_TYPE_FID 1
#define FAN_EVENT_INFO_TYPE_DFID_NAME 2 #define FAN_EVENT_INFO_TYPE_DFID_NAME 2
#define FAN_EVENT_INFO_TYPE_DFID 3
/* Variable length info record following event metadata */ /* Variable length info record following event metadata */
struct fanotify_event_info_header { struct fanotify_event_info_header {
@ -127,10 +132,11 @@ struct fanotify_event_info_header {
}; };
/* /*
* Unique file identifier info record. This is used both for * Unique file identifier info record.
* FAN_EVENT_INFO_TYPE_FID records and for FAN_EVENT_INFO_TYPE_DFID_NAME * This structure is used for records of types FAN_EVENT_INFO_TYPE_FID,
* records. For FAN_EVENT_INFO_TYPE_DFID_NAME there is additionally a null * FAN_EVENT_INFO_TYPE_DFID and FAN_EVENT_INFO_TYPE_DFID_NAME.
* terminated name immediately after the file handle. * For FAN_EVENT_INFO_TYPE_DFID_NAME there is additionally a null terminated
* name immediately after the file handle.
*/ */
struct fanotify_event_info_fid { struct fanotify_event_info_fid {
struct fanotify_event_info_header hdr; struct fanotify_event_info_header hdr;

View File

@ -36,7 +36,7 @@ static struct fsnotify_group *audit_fsnotify_group;
/* fsnotify events we care about. */ /* fsnotify events we care about. */
#define AUDIT_FS_EVENTS (FS_MOVE | FS_CREATE | FS_DELETE | FS_DELETE_SELF |\ #define AUDIT_FS_EVENTS (FS_MOVE | FS_CREATE | FS_DELETE | FS_DELETE_SELF |\
FS_MOVE_SELF | FS_EVENT_ON_CHILD) FS_MOVE_SELF)
static void audit_fsnotify_mark_free(struct audit_fsnotify_mark *audit_mark) static void audit_fsnotify_mark_free(struct audit_fsnotify_mark *audit_mark)
{ {
@ -152,35 +152,31 @@ static void audit_autoremove_mark_rule(struct audit_fsnotify_mark *audit_mark)
} }
/* Update mark data in audit rules based on fsnotify events. */ /* Update mark data in audit rules based on fsnotify events. */
static int audit_mark_handle_event(struct fsnotify_group *group, static int audit_mark_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
struct inode *to_tell, struct inode *inode, struct inode *dir,
u32 mask, const void *data, int data_type, const struct qstr *dname)
const struct qstr *dname, u32 cookie,
struct fsnotify_iter_info *iter_info)
{ {
struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
struct audit_fsnotify_mark *audit_mark; struct audit_fsnotify_mark *audit_mark;
const struct inode *inode = fsnotify_data_inode(data, data_type);
audit_mark = container_of(inode_mark, struct audit_fsnotify_mark, mark); audit_mark = container_of(inode_mark, struct audit_fsnotify_mark, mark);
BUG_ON(group != audit_fsnotify_group); if (WARN_ON_ONCE(inode_mark->group != audit_fsnotify_group) ||
WARN_ON_ONCE(!inode))
if (WARN_ON(!inode))
return 0; return 0;
if (mask & (FS_CREATE|FS_MOVED_TO|FS_DELETE|FS_MOVED_FROM)) { if (mask & (FS_CREATE|FS_MOVED_TO|FS_DELETE|FS_MOVED_FROM)) {
if (audit_compare_dname_path(dname, audit_mark->path, AUDIT_NAME_FULL)) if (audit_compare_dname_path(dname, audit_mark->path, AUDIT_NAME_FULL))
return 0; return 0;
audit_update_mark(audit_mark, inode); audit_update_mark(audit_mark, inode);
} else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF)) } else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF)) {
audit_autoremove_mark_rule(audit_mark); audit_autoremove_mark_rule(audit_mark);
}
return 0; return 0;
} }
static const struct fsnotify_ops audit_mark_fsnotify_ops = { static const struct fsnotify_ops audit_mark_fsnotify_ops = {
.handle_event = audit_mark_handle_event, .handle_inode_event = audit_mark_handle_event,
.free_mark = audit_fsnotify_free_mark, .free_mark = audit_fsnotify_free_mark,
}; };

View File

@ -1035,11 +1035,9 @@ static void evict_chunk(struct audit_chunk *chunk)
audit_schedule_prune(); audit_schedule_prune();
} }
static int audit_tree_handle_event(struct fsnotify_group *group, static int audit_tree_handle_event(struct fsnotify_mark *mark, u32 mask,
struct inode *to_tell, struct inode *inode, struct inode *dir,
u32 mask, const void *data, int data_type, const struct qstr *file_name)
const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info)
{ {
return 0; return 0;
} }
@ -1068,7 +1066,7 @@ static void audit_tree_freeing_mark(struct fsnotify_mark *mark,
} }
static const struct fsnotify_ops audit_tree_ops = { static const struct fsnotify_ops audit_tree_ops = {
.handle_event = audit_tree_handle_event, .handle_inode_event = audit_tree_handle_event,
.freeing_mark = audit_tree_freeing_mark, .freeing_mark = audit_tree_freeing_mark,
.free_mark = audit_tree_destroy_watch, .free_mark = audit_tree_destroy_watch,
}; };

View File

@ -53,7 +53,7 @@ static struct fsnotify_group *audit_watch_group;
/* fsnotify events we care about. */ /* fsnotify events we care about. */
#define AUDIT_FS_WATCH (FS_MOVE | FS_CREATE | FS_DELETE | FS_DELETE_SELF |\ #define AUDIT_FS_WATCH (FS_MOVE | FS_CREATE | FS_DELETE | FS_DELETE_SELF |\
FS_MOVE_SELF | FS_EVENT_ON_CHILD | FS_UNMOUNT) FS_MOVE_SELF | FS_UNMOUNT)
static void audit_free_parent(struct audit_parent *parent) static void audit_free_parent(struct audit_parent *parent)
{ {
@ -464,20 +464,17 @@ void audit_remove_watch_rule(struct audit_krule *krule)
} }
/* Update watch data in audit rules based on fsnotify events. */ /* Update watch data in audit rules based on fsnotify events. */
static int audit_watch_handle_event(struct fsnotify_group *group, static int audit_watch_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
struct inode *to_tell, struct inode *inode, struct inode *dir,
u32 mask, const void *data, int data_type, const struct qstr *dname)
const struct qstr *dname, u32 cookie,
struct fsnotify_iter_info *iter_info)
{ {
struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
const struct inode *inode = fsnotify_data_inode(data, data_type);
struct audit_parent *parent; struct audit_parent *parent;
parent = container_of(inode_mark, struct audit_parent, mark); parent = container_of(inode_mark, struct audit_parent, mark);
BUG_ON(group != audit_watch_group); if (WARN_ON_ONCE(inode_mark->group != audit_watch_group) ||
WARN_ON(!inode); WARN_ON_ONCE(!inode))
return 0;
if (mask & (FS_CREATE|FS_MOVED_TO) && inode) if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0); audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0);
@ -490,7 +487,7 @@ static int audit_watch_handle_event(struct fsnotify_group *group,
} }
static const struct fsnotify_ops audit_watch_fsnotify_ops = { static const struct fsnotify_ops audit_watch_fsnotify_ops = {
.handle_event = audit_watch_handle_event, .handle_inode_event = audit_watch_handle_event,
.free_mark = audit_watch_free_mark, .free_mark = audit_watch_free_mark,
}; };

View File

@ -1543,8 +1543,7 @@ static void latency_fsnotify_workfn(struct work_struct *work)
{ {
struct trace_array *tr = container_of(work, struct trace_array, struct trace_array *tr = container_of(work, struct trace_array,
fsnotify_work); fsnotify_work);
fsnotify(tr->d_max_latency->d_inode, FS_MODIFY, fsnotify_inode(tr->d_max_latency->d_inode, FS_MODIFY);
tr->d_max_latency->d_inode, FSNOTIFY_EVENT_INODE, NULL, 0);
} }
static void latency_fsnotify_workfn_irq(struct irq_work *iwork) static void latency_fsnotify_workfn_irq(struct irq_work *iwork)