-----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:
commit
eb65405eb6
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue