Revert "fsnotify: store struct file not struct path"
[sfrench/cifs-2.6.git] / fs / notify / fanotify / fanotify_user.c
index 66e38fc052b27abc8f1f0ee02d2799c8f5be9c08..032b837fcd11912e54a01920ddf40b9506f1c8f9 100644 (file)
@@ -1,3 +1,4 @@
+#include <linux/fanotify.h>
 #include <linux/fcntl.h>
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/poll.h>
 #include <linux/security.h>
 #include <linux/syscalls.h>
+#include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/uaccess.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;
+static struct kmem_cache *fanotify_response_event_cache __read_mostly;
+
+struct fanotify_response_event {
+       struct list_head list;
+       __s32 fd;
+       struct fsnotify_event *event;
+};
 
 /*
  * Get an fsnotify notification event if one exists and is small
@@ -72,7 +81,7 @@ static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event)
         * are NULL;  That's fine, just don't call dentry open */
        if (dentry && mnt)
                new_file = dentry_open(dentry, mnt,
-                                      O_RDONLY | O_LARGEFILE | FMODE_NONOTIFY,
+                                      group->fanotify_data.f_flags | FMODE_NONOTIFY,
                                       current_cred());
        else
                new_file = ERR_PTR(-EOVERFLOW);
@@ -102,30 +111,159 @@ static ssize_t fill_event_metadata(struct fsnotify_group *group,
 
        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 metadata->fd;
 }
 
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+static struct fanotify_response_event *dequeue_re(struct fsnotify_group *group,
+                                                 __s32 fd)
+{
+       struct fanotify_response_event *re, *return_re = NULL;
+
+       mutex_lock(&group->fanotify_data.access_mutex);
+       list_for_each_entry(re, &group->fanotify_data.access_list, list) {
+               if (re->fd != fd)
+                       continue;
+
+               list_del_init(&re->list);
+               return_re = re;
+               break;
+       }
+       mutex_unlock(&group->fanotify_data.access_mutex);
+
+       pr_debug("%s: found return_re=%p\n", __func__, return_re);
+
+       return return_re;
+}
+
+static int process_access_response(struct fsnotify_group *group,
+                                  struct fanotify_response *response_struct)
+{
+       struct fanotify_response_event *re;
+       __s32 fd = response_struct->fd;
+       __u32 response = response_struct->response;
+
+       pr_debug("%s: group=%p fd=%d response=%d\n", __func__, group,
+                fd, response);
+       /*
+        * make sure the response is valid, if invalid we do nothing and either
+        * userspace can send a valid responce or we will clean it up after the
+        * timeout
+        */
+       switch (response) {
+       case FAN_ALLOW:
+       case FAN_DENY:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (fd < 0)
+               return -EINVAL;
+
+       re = dequeue_re(group, fd);
+       if (!re)
+               return -ENOENT;
+
+       re->event->response = response;
+
+       wake_up(&group->fanotify_data.access_waitq);
+
+       kmem_cache_free(fanotify_response_event_cache, re);
+
+       return 0;
+}
+
+static int prepare_for_access_response(struct fsnotify_group *group,
+                                      struct fsnotify_event *event,
+                                      __s32 fd)
+{
+       struct fanotify_response_event *re;
+
+       if (!(event->mask & FAN_ALL_PERM_EVENTS))
+               return 0;
+
+       re = kmem_cache_alloc(fanotify_response_event_cache, GFP_KERNEL);
+       if (!re)
+               return -ENOMEM;
+
+       re->event = event;
+       re->fd = fd;
+
+       mutex_lock(&group->fanotify_data.access_mutex);
+       list_add_tail(&re->list, &group->fanotify_data.access_list);
+       mutex_unlock(&group->fanotify_data.access_mutex);
+
+       return 0;
+}
+
+static void remove_access_response(struct fsnotify_group *group,
+                                  struct fsnotify_event *event,
+                                  __s32 fd)
+{
+       struct fanotify_response_event *re;
+
+       if (!(event->mask & FAN_ALL_PERM_EVENTS))
+               return;
+
+       re = dequeue_re(group, fd);
+       if (!re)
+               return;
+
+       BUG_ON(re->event != event);
+
+       kmem_cache_free(fanotify_response_event_cache, re);
+
+       return;
+}
+#else
+static int prepare_for_access_response(struct fsnotify_group *group,
+                                      struct fsnotify_event *event,
+                                      __s32 fd)
+{
+       return 0;
+}
+
+static void remove_access_response(struct fsnotify_group *group,
+                                  struct fsnotify_event *event,
+                                  __s32 fd)
+{
+       return;
+}
+#endif
+
 static ssize_t copy_event_to_user(struct fsnotify_group *group,
                                  struct fsnotify_event *event,
                                  char __user *buf)
 {
        struct fanotify_event_metadata fanotify_event_metadata;
-       int ret;
+       int fd, ret;
 
        pr_debug("%s: group=%p event=%p\n", __func__, group, event);
 
-       ret = fill_event_metadata(group, &fanotify_event_metadata, event);
-       if (ret < 0)
-               return ret;
+       fd = fill_event_metadata(group, &fanotify_event_metadata, event);
+       if (fd < 0)
+               return fd;
 
+       ret = prepare_for_access_response(group, event, fd);
+       if (ret)
+               goto out_close_fd;
+
+       ret = -EFAULT;
        if (copy_to_user(buf, &fanotify_event_metadata, FAN_EVENT_METADATA_LEN))
-               return -EFAULT;
+               goto out_kill_access_response;
 
        return FAN_EVENT_METADATA_LEN;
+
+out_kill_access_response:
+       remove_access_response(group, event, fd);
+out_close_fd:
+       sys_close(fd);
+       return ret;
 }
 
 /* intofiy userspace file descriptor functions */
@@ -196,6 +334,33 @@ static ssize_t fanotify_read(struct file *file, char __user *buf,
        return ret;
 }
 
+static ssize_t fanotify_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
+{
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+       struct fanotify_response response = { .fd = -1, .response = -1 };
+       struct fsnotify_group *group;
+       int ret;
+
+       group = file->private_data;
+
+       if (count > sizeof(response))
+               count = sizeof(response);
+
+       pr_debug("%s: group=%p count=%zu\n", __func__, group, count);
+
+       if (copy_from_user(&response, buf, count))
+               return -EFAULT;
+
+       ret = process_access_response(group, &response);
+       if (ret < 0)
+               count = ret;
+
+       return count;
+#else
+       return -EINVAL;
+#endif
+}
+
 static int fanotify_release(struct inode *ignored, struct file *file)
 {
        struct fsnotify_group *group = file->private_data;
@@ -236,6 +401,7 @@ static long fanotify_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 static const struct file_operations fanotify_fops = {
        .poll           = fanotify_poll,
        .read           = fanotify_read,
+       .write          = fanotify_write,
        .fasync         = NULL,
        .release        = fanotify_release,
        .unlocked_ioctl = fanotify_ioctl,
@@ -295,146 +461,157 @@ out:
        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,
+                                           unsigned int flags)
 {
-       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;
+       if (!(flags & FAN_MARK_IGNORED_MASK)) {
+               oldmask = fsn_mark->mask;
+               fsnotify_set_mark_mask_locked(fsn_mark, (oldmask & ~mask));
+       } else {
+               oldmask = fsn_mark->ignored_mask;
+               fsnotify_set_mark_ignored_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,
+                                        unsigned int flags)
+{
+       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, flags);
        fsnotify_put_mark(fsn_mark);
+       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,
+                                     unsigned int flags)
 {
-       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;
-
-               ret = -ENOMEM;
-               new_fsn_mark = kmem_cache_alloc(fanotify_mark_cache, GFP_KERNEL);
-               if (!new_fsn_mark)
-                       goto out;
+       fsn_mark = fsnotify_find_inode_mark(group, inode);
+       if (!fsn_mark)
+               return -ENOENT;
 
-               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;
-               }
+       removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags);
+       /* matches the fsnotify_find_inode_mark() */
+       fsnotify_put_mark(fsn_mark);
+       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,
+                                      unsigned int flags)
+{
+       __u32 oldmask;
 
        spin_lock(&fsn_mark->lock);
-       old_mask = fsn_mark->mask;
-       fsn_mark->mask |= mask;
-       new_mask = fsn_mark->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);
+       if (!(flags & FAN_MARK_IGNORED_MASK)) {
+               oldmask = fsn_mark->mask;
+               fsnotify_set_mark_mask_locked(fsn_mark, (oldmask | mask));
+       } else {
+               oldmask = fsn_mark->ignored_mask;
+               fsnotify_set_mark_ignored_mask_locked(fsn_mark, (oldmask | mask));
+               if (flags & FAN_MARK_IGNORED_SURV_MODIFY)
+                       fsn_mark->flags |= FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY;
        }
+       spin_unlock(&fsn_mark->lock);
 
-       /* 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,
+                                     unsigned int flags)
 {
-       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, flags);
+       fsnotify_put_mark(fsn_mark);
+       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,
+                                  unsigned int flags)
 {
-       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, flags);
+       fsnotify_put_mark(fsn_mark);
+       if (added & ~inode->i_fsnotify_mask)
+               fsnotify_recalc_inode_mask(inode);
+       return 0;
 }
 
 /* fanotify syscalls */
-SYSCALL_DEFINE3(fanotify_init, unsigned int, flags, unsigned int, event_f_flags,
-               unsigned int, priority)
+SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
 {
        struct fsnotify_group *group;
        int f_flags, fd;
 
-       pr_debug("%s: flags=%d event_f_flags=%d priority=%d\n",
-               __func__, flags, event_f_flags, priority);
-
-       if (event_f_flags)
-               return -EINVAL;
-       if (priority)
-               return -EINVAL;
+       pr_debug("%s: flags=%d event_f_flags=%d\n",
+               __func__, flags, event_f_flags);
 
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
@@ -442,7 +619,7 @@ SYSCALL_DEFINE3(fanotify_init, unsigned int, flags, unsigned int, event_f_flags,
        if (flags & ~FAN_ALL_INIT_FLAGS)
                return -EINVAL;
 
-       f_flags = (O_RDONLY | FMODE_NONOTIFY);
+       f_flags = O_RDWR | FMODE_NONOTIFY;
        if (flags & FAN_CLOEXEC)
                f_flags |= O_CLOEXEC;
        if (flags & FAN_NONBLOCK)
@@ -453,6 +630,13 @@ SYSCALL_DEFINE3(fanotify_init, unsigned int, flags, unsigned int, event_f_flags,
        if (IS_ERR(group))
                return PTR_ERR(group);
 
+       group->fanotify_data.f_flags = event_f_flags;
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+       mutex_init(&group->fanotify_data.access_mutex);
+       init_waitqueue_head(&group->fanotify_data.access_waitq);
+       INIT_LIST_HEAD(&group->fanotify_data.access_list);
+#endif
+
        fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
        if (fd < 0)
                goto out_put_group;
@@ -468,7 +652,8 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
                              __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;
@@ -481,7 +666,21 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
        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 | FAN_MARK_FLUSH)) {
+       case FAN_MARK_ADD:
+       case FAN_MARK_REMOVE:
+       case FAN_MARK_FLUSH:
+               break;
+       default:
+               return -EINVAL;
+       }
+#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
+       if (mask & ~(FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_EVENT_ON_CHILD))
+#else
+       if (mask & ~(FAN_ALL_EVENTS | FAN_EVENT_ON_CHILD))
+#endif
                return -EINVAL;
 
        filp = fget_light(fanotify_fd, &fput_needed);
@@ -498,11 +697,35 @@ SYSCALL_DEFINE(fanotify_mark)(int fanotify_fd, unsigned int flags,
                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 | FAN_MARK_FLUSH)) {
+       case FAN_MARK_ADD:
+               if (flags & FAN_MARK_MOUNT)
+                       ret = fanotify_add_vfsmount_mark(group, mnt, mask, flags);
+               else
+                       ret = fanotify_add_inode_mark(group, inode, mask, flags);
+               break;
+       case FAN_MARK_REMOVE:
+               if (flags & FAN_MARK_MOUNT)
+                       ret = fanotify_remove_vfsmount_mark(group, mnt, mask, flags);
+               else
+                       ret = fanotify_remove_inode_mark(group, inode, mask, flags);
+               break;
+       case FAN_MARK_FLUSH:
+               if (flags & FAN_MARK_MOUNT)
+                       fsnotify_clear_vfsmount_marks_by_group(group);
+               else
+                       fsnotify_clear_inode_marks_by_group(group);
+               break;
+       default:
+               ret = -EINVAL;
+       }
 
        path_put(&path);
 fput_and_out:
@@ -529,6 +752,8 @@ SYSCALL_ALIAS(sys_fanotify_mark, SyS_fanotify_mark);
 static int __init fanotify_user_setup(void)
 {
        fanotify_mark_cache = KMEM_CACHE(fsnotify_mark, SLAB_PANIC);
+       fanotify_response_event_cache = KMEM_CACHE(fanotify_response_event,
+                                                  SLAB_PANIC);
 
        return 0;
 }