+#include <linux/fanotify.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <asm/ioctls.h>
-#include "fanotify.h"
+extern const struct fsnotify_ops fanotify_fsnotify_ops;
static struct kmem_cache *fanotify_mark_cache __read_mostly;
metadata->event_len = FAN_EVENT_METADATA_LEN;
metadata->vers = FANOTIFY_METADATA_VERSION;
- metadata->mask = fanotify_outgoing_mask(event->mask);
+ metadata->mask = event->mask & FAN_ALL_OUTGOING_EVENTS;
metadata->pid = pid_vnr(event->tgid);
metadata->fd = create_fd(group, event);
return ret;
}
-static int fanotify_remove_mark(struct fsnotify_group *group,
- struct inode *inode,
- __u32 mask)
+static __u32 fanotify_mark_remove_from_mask(struct fsnotify_mark *fsn_mark, __u32 mask)
{
- struct fsnotify_mark *fsn_mark;
- __u32 new_mask;
-
- pr_debug("%s: group=%p inode=%p mask=%x\n", __func__,
- group, inode, mask);
-
- fsn_mark = fsnotify_find_mark(group, inode);
- if (!fsn_mark)
- return -ENOENT;
+ __u32 oldmask;
spin_lock(&fsn_mark->lock);
- fsn_mark->mask &= ~mask;
- new_mask = fsn_mark->mask;
+ oldmask = fsn_mark->mask;
+ fsnotify_set_mark_mask_locked(fsn_mark, (oldmask & ~mask));
spin_unlock(&fsn_mark->lock);
- if (!new_mask)
+ if (!(oldmask & ~mask))
fsnotify_destroy_mark(fsn_mark);
- else
- fsnotify_recalc_inode_mask(inode);
- fsnotify_recalc_group_mask(group);
+ return mask & oldmask;
+}
+
+static int fanotify_remove_vfsmount_mark(struct fsnotify_group *group,
+ struct vfsmount *mnt, __u32 mask)
+{
+ struct fsnotify_mark *fsn_mark = NULL;
+ __u32 removed;
- /* matches the fsnotify_find_mark() */
+ fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
+ if (!fsn_mark)
+ return -ENOENT;
+
+ removed = fanotify_mark_remove_from_mask(fsn_mark, mask);
fsnotify_put_mark(fsn_mark);
+ if (removed & group->mask)
+ fsnotify_recalc_group_mask(group);
+ if (removed & mnt->mnt_fsnotify_mask)
+ fsnotify_recalc_vfsmount_mask(mnt);
return 0;
}
-static int fanotify_add_mark(struct fsnotify_group *group,
- struct inode *inode,
- __u32 mask)
+static int fanotify_remove_inode_mark(struct fsnotify_group *group,
+ struct inode *inode, __u32 mask)
{
- struct fsnotify_mark *fsn_mark;
- __u32 old_mask, new_mask;
- int ret;
-
- pr_debug("%s: group=%p inode=%p mask=%x\n", __func__,
- group, inode, mask);
+ struct fsnotify_mark *fsn_mark = NULL;
+ __u32 removed;
- fsn_mark = fsnotify_find_mark(group, inode);
- if (!fsn_mark) {
- struct fsnotify_mark *new_fsn_mark;
+ fsn_mark = fsnotify_find_inode_mark(group, inode);
+ if (!fsn_mark)
+ return -ENOENT;
- ret = -ENOMEM;
- new_fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
- if (!new_fsn_mark)
- goto out;
+ removed = fanotify_mark_remove_from_mask(fsn_mark, mask);
+ /* matches the fsnotify_find_inode_mark() */
+ fsnotify_put_mark(fsn_mark);
- fsnotify_init_mark(new_fsn_mark, fanotify_free_mark);
- ret = fsnotify_add_mark(new_fsn_mark, group, inode, 0);
- if (ret) {
- fanotify_free_mark(new_fsn_mark);
- goto out;
- }
+ if (removed & group->mask)
+ fsnotify_recalc_group_mask(group);
+ if (removed & inode->i_fsnotify_mask)
+ fsnotify_recalc_inode_mask(inode);
- fsn_mark = new_fsn_mark;
- }
+ return 0;
+}
- ret = 0;
+static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark, __u32 mask)
+{
+ __u32 oldmask;
spin_lock(&fsn_mark->lock);
- old_mask = fsn_mark->mask;
- fsn_mark->mask |= mask;
- new_mask = fsn_mark->mask;
+ oldmask = fsn_mark->mask;
+ fsnotify_set_mark_mask_locked(fsn_mark, (oldmask | mask));
spin_unlock(&fsn_mark->lock);
- /* we made changes to a mask, update the group mask and the inode mask
- * so things happen quickly. */
- if (old_mask != new_mask) {
- /* more bits in old than in new? */
- int dropped = (old_mask & ~new_mask);
- /* more bits in this mark than the inode's mask? */
- int do_inode = (new_mask & ~inode->i_fsnotify_mask);
- /* more bits in this mark than the group? */
- int do_group = (new_mask & ~group->mask);
-
- /* update the inode with this new mark */
- if (dropped || do_inode)
- fsnotify_recalc_inode_mask(inode);
-
- /* update the group mask with the new mask */
- if (dropped || do_group)
- fsnotify_recalc_group_mask(group);
- }
-
- /* match the init or the find.... */
- fsnotify_put_mark(fsn_mark);
-out:
- return ret;
+ return mask & ~oldmask;
}
-static int fanotify_update_mark(struct fsnotify_group *group,
- struct inode *inode, int flags,
- __u32 mask)
+static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
+ struct vfsmount *mnt, __u32 mask)
{
- pr_debug("%s: group=%p inode=%p flags=%x mask=%x\n", __func__,
- group, inode, flags, mask);
+ struct fsnotify_mark *fsn_mark;
+ __u32 added;
- if (flags & FAN_MARK_ADD)
- fanotify_add_mark(group, inode, mask);
- else if (flags & FAN_MARK_REMOVE)
- fanotify_remove_mark(group, inode, mask);
- else
- BUG();
+ fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
+ if (!fsn_mark) {
+ int ret;
+ fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
+ if (!fsn_mark)
+ return -ENOMEM;
+
+ fsnotify_init_mark(fsn_mark, fanotify_free_mark);
+ ret = fsnotify_add_mark(fsn_mark, group, NULL, mnt, 0);
+ if (ret) {
+ fanotify_free_mark(fsn_mark);
+ return ret;
+ }
+ }
+ added = fanotify_mark_add_to_mask(fsn_mark, mask);
+ fsnotify_put_mark(fsn_mark);
+ if (added) {
+ if (added & ~group->mask)
+ fsnotify_recalc_group_mask(group);
+ if (added & ~mnt->mnt_fsnotify_mask)
+ fsnotify_recalc_vfsmount_mask(mnt);
+ }
return 0;
}
-static bool fanotify_mark_validate_input(int flags,
- __u32 mask)
+static int fanotify_add_inode_mark(struct fsnotify_group *group,
+ struct inode *inode, __u32 mask)
{
- pr_debug("%s: flags=%x mask=%x\n", __func__, flags, mask);
-
- /* are flags valid of this operation? */
- if (!fanotify_mark_flags_valid(flags))
- return false;
- /* is the mask valid? */
- if (!fanotify_mask_valid(mask))
- return false;
- return true;
+ struct fsnotify_mark *fsn_mark;
+ __u32 added;
+
+ pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);
+
+ fsn_mark = fsnotify_find_inode_mark(group, inode);
+ if (!fsn_mark) {
+ int ret;
+
+ fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
+ if (!fsn_mark)
+ return -ENOMEM;
+
+ fsnotify_init_mark(fsn_mark, fanotify_free_mark);
+ ret = fsnotify_add_mark(fsn_mark, group, inode, NULL, 0);
+ if (ret) {
+ fanotify_free_mark(fsn_mark);
+ return ret;
+ }
+ }
+ added = fanotify_mark_add_to_mask(fsn_mark, mask);
+ fsnotify_put_mark(fsn_mark);
+ if (added) {
+ if (added & ~group->mask)
+ fsnotify_recalc_group_mask(group);
+ if (added & ~inode->i_fsnotify_mask)
+ fsnotify_recalc_inode_mask(inode);
+ }
+ return 0;
}
/* fanotify syscalls */
__u64 mask, int dfd,
const char __user * pathname)
{
- struct inode *inode;
+ struct inode *inode = NULL;
+ struct vfsmount *mnt = NULL;
struct fsnotify_group *group;
struct file *filp;
struct path path;
if (mask & ((__u64)0xffffffff << 32))
return -EINVAL;
- if (!fanotify_mark_validate_input(flags, mask))
+ if (flags & ~FAN_ALL_MARK_FLAGS)
+ return -EINVAL;
+ switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
+ case FAN_MARK_ADD:
+ case FAN_MARK_REMOVE:
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (mask & ~(FAN_ALL_EVENTS | FAN_EVENT_ON_CHILD))
return -EINVAL;
filp = fget_light(fanotify_fd, &fput_needed);
goto fput_and_out;
/* inode held in place by reference to path; group by fget on fd */
- inode = path.dentry->d_inode;
+ if (!(flags & FAN_MARK_MOUNT))
+ inode = path.dentry->d_inode;
+ else
+ mnt = path.mnt;
group = filp->private_data;
/* create/update an inode mark */
- ret = fanotify_update_mark(group, inode, flags, mask);
+ switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
+ case FAN_MARK_ADD:
+ if (flags & FAN_MARK_MOUNT)
+ ret = fanotify_add_vfsmount_mark(group, mnt, mask);
+ else
+ ret = fanotify_add_inode_mark(group, inode, mask);
+ break;
+ case FAN_MARK_REMOVE:
+ if (flags & FAN_MARK_MOUNT)
+ ret = fanotify_remove_vfsmount_mark(group, mnt, mask);
+ else
+ ret = fanotify_remove_inode_mark(group, inode, mask);
+ break;
+ default:
+ ret = -EINVAL;
+ }
path_put(&path);
fput_and_out: