fsnotify: allow marks to not pin inodes in core
[linux-2.6-block.git] / fs / notify / fanotify / fanotify_user.c
index a99550f83f8aff4888c757a5440452e30724d95d..3320f0c57e31901874afebbdf7826ccf7c3baf1f 100644 (file)
@@ -1,3 +1,4 @@
+#include <linux/fanotify.h>
 #include <linux/fcntl.h>
 #include <linux/file.h>
 #include <linux/fs.h>
@@ -14,7 +15,7 @@
 
 #include <asm/ioctls.h>
 
-#include "fanotify.h"
+extern const struct fsnotify_ops fanotify_fsnotify_ops;
 
 static struct kmem_cache *fanotify_mark_cache __read_mostly;
 
@@ -43,17 +44,14 @@ static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
        return fsnotify_remove_notify_event(group);
 }
 
-static int create_and_fill_fd(struct fsnotify_group *group,
-                             struct fanotify_event_metadata *metadata,
-                             struct fsnotify_event *event)
+static int create_fd(struct fsnotify_group *group, struct fsnotify_event *event)
 {
        int client_fd;
        struct dentry *dentry;
        struct vfsmount *mnt;
        struct file *new_file;
 
-       pr_debug("%s: group=%p metadata=%p event=%p\n", __func__, group,
-                metadata, event);
+       pr_debug("%s: group=%p event=%p\n", __func__, group, event);
 
        client_fd = get_unused_fd();
        if (client_fd < 0)
@@ -93,9 +91,7 @@ static int create_and_fill_fd(struct fsnotify_group *group,
                fd_install(client_fd, new_file);
        }
 
-       metadata->fd = client_fd;
-
-       return 0;
+       return client_fd;
 }
 
 static ssize_t fill_event_metadata(struct fsnotify_group *group,
@@ -107,10 +103,11 @@ 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);
-
-       return create_and_fill_fd(group, metadata, event);
+       metadata->mask = event->mask & FAN_ALL_OUTGOING_EVENTS;
+       metadata->pid = pid_vnr(event->tgid);
+       metadata->fd = create_fd(group, event);
 
+       return metadata->fd;
 }
 
 static ssize_t copy_event_to_user(struct fsnotify_group *group,
@@ -123,7 +120,7 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
        pr_debug("%s: group=%p event=%p\n", __func__, group, event);
 
        ret = fill_event_metadata(group, &fanotify_event_metadata, event);
-       if (ret)
+       if (ret < 0)
                return ret;
 
        if (copy_to_user(buf, &fanotify_event_metadata, FAN_EVENT_METADATA_LEN))
@@ -299,130 +296,139 @@ 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)
 {
-       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;
+
+       fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
+       if (!fsn_mark)
+               return -ENOENT;
 
-       /* matches the fsnotify_find_mark() */
+       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;
+       struct fsnotify_mark *fsn_mark = NULL;
+       __u32 removed;
 
-       pr_debug("%s: group=%p inode=%p mask=%x\n", __func__,
-                group, inode, mask);
-
-       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 */
@@ -468,10 +474,12 @@ out_put_group:
        return fd;
 }
 
-SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,
-               __u64, mask, int, dfd, const char  __user *, pathname)
+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;
@@ -484,7 +492,16 @@ SYSCALL_DEFINE5(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)) {
+       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);
@@ -501,11 +518,29 @@ SYSCALL_DEFINE5(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)) {
+       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:
@@ -513,6 +548,17 @@ fput_and_out:
        return ret;
 }
 
+#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS
+asmlinkage long SyS_fanotify_mark(long fanotify_fd, long flags, __u64 mask,
+                                 long dfd, long pathname)
+{
+       return SYSC_fanotify_mark((int) fanotify_fd, (unsigned int) flags,
+                                 mask, (int) dfd,
+                                 (const char  __user *) pathname);
+}
+SYSCALL_ALIAS(sys_fanotify_mark, SyS_fanotify_mark);
+#endif
+
 /*
  * fanotify_user_setup - Our initialization function.  Note that we cannnot return
  * error because we have compiled-in VFS hooks.  So an (unlikely) failure here