diff options
Diffstat (limited to 'fs/notify/inotify/inotify_user.c')
| -rw-r--r-- | fs/notify/inotify/inotify_user.c | 1070 |
1 files changed, 547 insertions, 523 deletions
diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index bed766e435b..cc423a30a0c 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -8,6 +8,9 @@ * Copyright (C) 2005 John McCutchan * Copyright 2006 Hewlett-Packard Development Company, L.P. * + * Copyright (C) 2009 Eric Paris <Red Hat Inc> + * inotify was largely rewriten to make use of the fsnotify infrastructure + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any @@ -19,94 +22,34 @@ * General Public License for more details. */ -#include <linux/kernel.h> -#include <linux/sched.h> -#include <linux/slab.h> -#include <linux/fs.h> #include <linux/file.h> -#include <linux/mount.h> -#include <linux/namei.h> -#include <linux/poll.h> -#include <linux/init.h> -#include <linux/list.h> +#include <linux/fs.h> /* struct inode */ +#include <linux/fsnotify_backend.h> +#include <linux/idr.h> +#include <linux/init.h> /* module_init */ #include <linux/inotify.h> +#include <linux/kernel.h> /* roundup() */ +#include <linux/namei.h> /* LOOKUP_FOLLOW */ +#include <linux/sched.h> /* struct user */ +#include <linux/slab.h> /* struct kmem_cache */ #include <linux/syscalls.h> -#include <linux/magic.h> - -#include <asm/ioctls.h> +#include <linux/types.h> +#include <linux/anon_inodes.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#include <linux/wait.h> -static struct kmem_cache *watch_cachep __read_mostly; -static struct kmem_cache *event_cachep __read_mostly; +#include "inotify.h" +#include "../fdinfo.h" -static struct vfsmount *inotify_mnt __read_mostly; +#include <asm/ioctls.h> /* these are configurable via /proc/sys/fs/inotify/ */ static int inotify_max_user_instances __read_mostly; -static int inotify_max_user_watches __read_mostly; static int inotify_max_queued_events __read_mostly; +static int inotify_max_user_watches __read_mostly; -/* - * Lock ordering: - * - * inotify_dev->up_mutex (ensures we don't re-add the same watch) - * inode->inotify_mutex (protects inode's watch list) - * inotify_handle->mutex (protects inotify_handle's watch list) - * inotify_dev->ev_mutex (protects device's event queue) - */ - -/* - * Lifetimes of the main data structures: - * - * inotify_device: Lifetime is managed by reference count, from - * sys_inotify_init() until release. Additional references can bump the count - * via get_inotify_dev() and drop the count via put_inotify_dev(). - * - * inotify_user_watch: Lifetime is from create_watch() to the receipt of an - * IN_IGNORED event from inotify, or when using IN_ONESHOT, to receipt of the - * first event, or to inotify_destroy(). - */ - -/* - * struct inotify_device - represents an inotify instance - * - * This structure is protected by the mutex 'mutex'. - */ -struct inotify_device { - wait_queue_head_t wq; /* wait queue for i/o */ - struct mutex ev_mutex; /* protects event queue */ - struct mutex up_mutex; /* synchronizes watch updates */ - struct list_head events; /* list of queued events */ - struct user_struct *user; /* user who opened this dev */ - struct inotify_handle *ih; /* inotify handle */ - struct fasync_struct *fa; /* async notification */ - atomic_t count; /* reference count */ - unsigned int queue_size; /* size of the queue (bytes) */ - unsigned int event_count; /* number of pending events */ - unsigned int max_events; /* maximum number of events */ -}; - -/* - * struct inotify_kernel_event - An inotify event, originating from a watch and - * queued for user-space. A list of these is attached to each instance of the - * device. In read(), this list is walked and all events that can fit in the - * buffer are returned. - * - * Protected by dev->ev_mutex of the device in which we are queued. - */ -struct inotify_kernel_event { - struct inotify_event event; /* the user-space event */ - struct list_head list; /* entry in inotify_device's list */ - char *name; /* filename, if any */ -}; - -/* - * struct inotify_user_watch - our version of an inotify_watch, we add - * a reference to the associated inotify_device. - */ -struct inotify_user_watch { - struct inotify_device *dev; /* associated device */ - struct inotify_watch wdata; /* inotify watch data */ -}; +static struct kmem_cache *inotify_inode_mark_cachep __read_mostly; #ifdef CONFIG_SYSCTL @@ -114,257 +57,280 @@ struct inotify_user_watch { static int zero; -ctl_table inotify_table[] = { +struct ctl_table inotify_table[] = { { - .ctl_name = INOTIFY_MAX_USER_INSTANCES, .procname = "max_user_instances", .data = &inotify_max_user_instances, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = &proc_dointvec_minmax, - .strategy = &sysctl_intvec, + .proc_handler = proc_dointvec_minmax, .extra1 = &zero, }, { - .ctl_name = INOTIFY_MAX_USER_WATCHES, .procname = "max_user_watches", .data = &inotify_max_user_watches, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = &proc_dointvec_minmax, - .strategy = &sysctl_intvec, + .proc_handler = proc_dointvec_minmax, .extra1 = &zero, }, { - .ctl_name = INOTIFY_MAX_QUEUED_EVENTS, .procname = "max_queued_events", .data = &inotify_max_queued_events, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = &proc_dointvec_minmax, - .strategy = &sysctl_intvec, + .proc_handler = proc_dointvec_minmax, .extra1 = &zero }, - { .ctl_name = 0 } + { } }; #endif /* CONFIG_SYSCTL */ -static inline void get_inotify_dev(struct inotify_device *dev) +static inline __u32 inotify_arg_to_mask(u32 arg) { - atomic_inc(&dev->count); + __u32 mask; + + /* + * everything should accept their own ignored, cares about children, + * and should receive events when the inode is unmounted + */ + mask = (FS_IN_IGNORED | FS_EVENT_ON_CHILD | FS_UNMOUNT); + + /* mask off the flags used to open the fd */ + mask |= (arg & (IN_ALL_EVENTS | IN_ONESHOT | IN_EXCL_UNLINK)); + + return mask; } -static inline void put_inotify_dev(struct inotify_device *dev) +static inline u32 inotify_mask_to_arg(__u32 mask) { - if (atomic_dec_and_test(&dev->count)) { - atomic_dec(&dev->user->inotify_devs); - free_uid(dev->user); - kfree(dev); - } + return mask & (IN_ALL_EVENTS | IN_ISDIR | IN_UNMOUNT | IN_IGNORED | + IN_Q_OVERFLOW); } -/* - * free_inotify_user_watch - cleans up the watch and its references - */ -static void free_inotify_user_watch(struct inotify_watch *w) +/* intofiy userspace file descriptor functions */ +static unsigned int inotify_poll(struct file *file, poll_table *wait) { - struct inotify_user_watch *watch; - struct inotify_device *dev; + struct fsnotify_group *group = file->private_data; + int ret = 0; + + poll_wait(file, &group->notification_waitq, wait); + mutex_lock(&group->notification_mutex); + if (!fsnotify_notify_queue_is_empty(group)) + ret = POLLIN | POLLRDNORM; + mutex_unlock(&group->notification_mutex); + + return ret; +} - watch = container_of(w, struct inotify_user_watch, wdata); - dev = watch->dev; +static int round_event_name_len(struct fsnotify_event *fsn_event) +{ + struct inotify_event_info *event; - atomic_dec(&dev->user->inotify_watches); - put_inotify_dev(dev); - kmem_cache_free(watch_cachep, watch); + event = INOTIFY_E(fsn_event); + if (!event->name_len) + return 0; + return roundup(event->name_len + 1, sizeof(struct inotify_event)); } /* - * kernel_event - create a new kernel event with the given parameters + * Get an inotify_kernel_event if one exists and is small + * enough to fit in "count". Return an error pointer if + * not large enough. * - * This function can sleep. + * Called with the group->notification_mutex held. */ -static struct inotify_kernel_event * kernel_event(s32 wd, u32 mask, u32 cookie, - const char *name) +static struct fsnotify_event *get_one_event(struct fsnotify_group *group, + size_t count) { - struct inotify_kernel_event *kevent; + size_t event_size = sizeof(struct inotify_event); + struct fsnotify_event *event; - kevent = kmem_cache_alloc(event_cachep, GFP_NOFS); - if (unlikely(!kevent)) + if (fsnotify_notify_queue_is_empty(group)) return NULL; - /* we hand this out to user-space, so zero it just in case */ - memset(&kevent->event, 0, sizeof(struct inotify_event)); - - kevent->event.wd = wd; - kevent->event.mask = mask; - kevent->event.cookie = cookie; - - INIT_LIST_HEAD(&kevent->list); - - if (name) { - size_t len, rem, event_size = sizeof(struct inotify_event); - - /* - * We need to pad the filename so as to properly align an - * array of inotify_event structures. Because the structure is - * small and the common case is a small filename, we just round - * up to the next multiple of the structure's sizeof. This is - * simple and safe for all architectures. - */ - len = strlen(name) + 1; - rem = event_size - len; - if (len > event_size) { - rem = event_size - (len % event_size); - if (len % event_size == 0) - rem = 0; - } + event = fsnotify_peek_notify_event(group); - kevent->name = kmalloc(len + rem, GFP_KERNEL); - if (unlikely(!kevent->name)) { - kmem_cache_free(event_cachep, kevent); - return NULL; - } - memcpy(kevent->name, name, len); - if (rem) - memset(kevent->name + len, 0, rem); - kevent->event.len = len + rem; - } else { - kevent->event.len = 0; - kevent->name = NULL; - } + pr_debug("%s: group=%p event=%p\n", __func__, group, event); - return kevent; -} + event_size += round_event_name_len(event); + if (event_size > count) + return ERR_PTR(-EINVAL); -/* - * inotify_dev_get_event - return the next event in the given dev's queue - * - * Caller must hold dev->ev_mutex. - */ -static inline struct inotify_kernel_event * -inotify_dev_get_event(struct inotify_device *dev) -{ - return list_entry(dev->events.next, struct inotify_kernel_event, list); -} + /* held the notification_mutex the whole time, so this is the + * same event we peeked above */ + fsnotify_remove_notify_event(group); -/* - * inotify_dev_get_last_event - return the last event in the given dev's queue - * - * Caller must hold dev->ev_mutex. - */ -static inline struct inotify_kernel_event * -inotify_dev_get_last_event(struct inotify_device *dev) -{ - if (list_empty(&dev->events)) - return NULL; - return list_entry(dev->events.prev, struct inotify_kernel_event, list); + return event; } /* - * inotify_dev_queue_event - event handler registered with core inotify, adds - * a new event to the given device + * Copy an event to user space, returning how much we copied. * - * Can sleep (calls kernel_event()). + * We already checked that the event size is smaller than the + * buffer we had in "get_one_event()" above. */ -static void inotify_dev_queue_event(struct inotify_watch *w, u32 wd, u32 mask, - u32 cookie, const char *name, - struct inode *ignored) +static ssize_t copy_event_to_user(struct fsnotify_group *group, + struct fsnotify_event *fsn_event, + char __user *buf) { - struct inotify_user_watch *watch; - struct inotify_device *dev; - struct inotify_kernel_event *kevent, *last; + struct inotify_event inotify_event; + struct inotify_event_info *event; + size_t event_size = sizeof(struct inotify_event); + size_t name_len; + size_t pad_name_len; - watch = container_of(w, struct inotify_user_watch, wdata); - dev = watch->dev; + pr_debug("%s: group=%p event=%p\n", __func__, group, fsn_event); - mutex_lock(&dev->ev_mutex); + event = INOTIFY_E(fsn_event); + name_len = event->name_len; + /* + * round up name length so it is a multiple of event_size + * plus an extra byte for the terminating '\0'. + */ + pad_name_len = round_event_name_len(fsn_event); + inotify_event.len = pad_name_len; + inotify_event.mask = inotify_mask_to_arg(fsn_event->mask); + inotify_event.wd = event->wd; + inotify_event.cookie = event->sync_cookie; + + /* send the main event */ + if (copy_to_user(buf, &inotify_event, event_size)) + return -EFAULT; + + buf += event_size; - /* we can safely put the watch as we don't reference it while - * generating the event + /* + * fsnotify only stores the pathname, so here we have to send the pathname + * and then pad that pathname out to a multiple of sizeof(inotify_event) + * with zeros. */ - if (mask & IN_IGNORED || w->mask & IN_ONESHOT) - put_inotify_watch(w); /* final put */ - - /* coalescing: drop this event if it is a dupe of the previous */ - last = inotify_dev_get_last_event(dev); - if (last && last->event.mask == mask && last->event.wd == wd && - last->event.cookie == cookie) { - const char *lastname = last->name; - - if (!name && !lastname) - goto out; - if (name && lastname && !strcmp(lastname, name)) - goto out; + if (pad_name_len) { + /* copy the path name */ + if (copy_to_user(buf, event->name, name_len)) + return -EFAULT; + buf += name_len; + + /* fill userspace with 0's */ + if (clear_user(buf, pad_name_len - name_len)) + return -EFAULT; + event_size += pad_name_len; } - /* the queue overflowed and we already sent the Q_OVERFLOW event */ - if (unlikely(dev->event_count > dev->max_events)) - goto out; + return event_size; +} - /* if the queue overflows, we need to notify user space */ - if (unlikely(dev->event_count == dev->max_events)) - kevent = kernel_event(-1, IN_Q_OVERFLOW, cookie, NULL); - else - kevent = kernel_event(wd, mask, cookie, name); +static ssize_t inotify_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct fsnotify_group *group; + struct fsnotify_event *kevent; + char __user *start; + int ret; + DEFINE_WAIT(wait); - if (unlikely(!kevent)) - goto out; + start = buf; + group = file->private_data; - /* queue the event and wake up anyone waiting */ - dev->event_count++; - dev->queue_size += sizeof(struct inotify_event) + kevent->event.len; - list_add_tail(&kevent->list, &dev->events); - wake_up_interruptible(&dev->wq); - kill_fasync(&dev->fa, SIGIO, POLL_IN); + while (1) { + prepare_to_wait(&group->notification_waitq, &wait, TASK_INTERRUPTIBLE); -out: - mutex_unlock(&dev->ev_mutex); -} + mutex_lock(&group->notification_mutex); + kevent = get_one_event(group, count); + mutex_unlock(&group->notification_mutex); -/* - * remove_kevent - cleans up the given kevent - * - * Caller must hold dev->ev_mutex. - */ -static void remove_kevent(struct inotify_device *dev, - struct inotify_kernel_event *kevent) -{ - list_del(&kevent->list); + pr_debug("%s: group=%p kevent=%p\n", __func__, group, kevent); + + if (kevent) { + ret = PTR_ERR(kevent); + if (IS_ERR(kevent)) + break; + ret = copy_event_to_user(group, kevent, buf); + fsnotify_destroy_event(group, kevent); + if (ret < 0) + break; + buf += ret; + count -= ret; + continue; + } + + ret = -EAGAIN; + if (file->f_flags & O_NONBLOCK) + break; + ret = -ERESTARTSYS; + if (signal_pending(current)) + break; - dev->event_count--; - dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len; + if (start != buf) + break; + + schedule(); + } + + finish_wait(&group->notification_waitq, &wait); + if (start != buf && ret != -EFAULT) + ret = buf - start; + return ret; } -/* - * free_kevent - frees the given kevent. - */ -static void free_kevent(struct inotify_kernel_event *kevent) +static int inotify_release(struct inode *ignored, struct file *file) { - kfree(kevent->name); - kmem_cache_free(event_cachep, kevent); + struct fsnotify_group *group = file->private_data; + + pr_debug("%s: group=%p\n", __func__, group); + + /* free this group, matching get was inotify_init->fsnotify_obtain_group */ + fsnotify_destroy_group(group); + + return 0; } -/* - * inotify_dev_event_dequeue - destroy an event on the given device - * - * Caller must hold dev->ev_mutex. - */ -static void inotify_dev_event_dequeue(struct inotify_device *dev) +static long inotify_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { - if (!list_empty(&dev->events)) { - struct inotify_kernel_event *kevent; - kevent = inotify_dev_get_event(dev); - remove_kevent(dev, kevent); - free_kevent(kevent); + struct fsnotify_group *group; + struct fsnotify_event *fsn_event; + void __user *p; + int ret = -ENOTTY; + size_t send_len = 0; + + group = file->private_data; + p = (void __user *) arg; + + pr_debug("%s: group=%p cmd=%u\n", __func__, group, cmd); + + switch (cmd) { + case FIONREAD: + mutex_lock(&group->notification_mutex); + list_for_each_entry(fsn_event, &group->notification_list, + list) { + send_len += sizeof(struct inotify_event); + send_len += round_event_name_len(fsn_event); + } + mutex_unlock(&group->notification_mutex); + ret = put_user(send_len, (int __user *) p); + break; } + + return ret; } +static const struct file_operations inotify_fops = { + .show_fdinfo = inotify_show_fdinfo, + .poll = inotify_poll, + .read = inotify_read, + .fasync = fsnotify_fasync, + .release = inotify_release, + .unlocked_ioctl = inotify_ioctl, + .compat_ioctl = inotify_ioctl, + .llseek = noop_llseek, +}; + + /* * find_inode - resolve a user-given path to a specific inode */ -static int find_inode(const char __user *dirname, struct path *path, - unsigned flags) +static int inotify_find_inode(const char __user *dirname, struct path *path, unsigned flags) { int error; @@ -378,224 +344,333 @@ static int find_inode(const char __user *dirname, struct path *path, return error; } -/* - * create_watch - creates a watch on the given device. - * - * Callers must hold dev->up_mutex. - */ -static int create_watch(struct inotify_device *dev, struct inode *inode, - u32 mask) +static int inotify_add_to_idr(struct idr *idr, spinlock_t *idr_lock, + struct inotify_inode_mark *i_mark) { - struct inotify_user_watch *watch; int ret; - if (atomic_read(&dev->user->inotify_watches) >= - inotify_max_user_watches) - return -ENOSPC; + idr_preload(GFP_KERNEL); + spin_lock(idr_lock); - watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL); - if (unlikely(!watch)) - return -ENOMEM; + ret = idr_alloc_cyclic(idr, i_mark, 1, 0, GFP_NOWAIT); + if (ret >= 0) { + /* we added the mark to the idr, take a reference */ + i_mark->wd = ret; + fsnotify_get_mark(&i_mark->fsn_mark); + } - /* save a reference to device and bump the count to make it official */ - get_inotify_dev(dev); - watch->dev = dev; + spin_unlock(idr_lock); + idr_preload_end(); + return ret < 0 ? ret : 0; +} - atomic_inc(&dev->user->inotify_watches); +static struct inotify_inode_mark *inotify_idr_find_locked(struct fsnotify_group *group, + int wd) +{ + struct idr *idr = &group->inotify_data.idr; + spinlock_t *idr_lock = &group->inotify_data.idr_lock; + struct inotify_inode_mark *i_mark; - inotify_init_watch(&watch->wdata); - ret = inotify_add_watch(dev->ih, &watch->wdata, inode, mask); - if (ret < 0) - free_inotify_user_watch(&watch->wdata); + assert_spin_locked(idr_lock); - return ret; + i_mark = idr_find(idr, wd); + if (i_mark) { + struct fsnotify_mark *fsn_mark = &i_mark->fsn_mark; + + fsnotify_get_mark(fsn_mark); + /* One ref for being in the idr, one ref we just took */ + BUG_ON(atomic_read(&fsn_mark->refcnt) < 2); + } + + return i_mark; } -/* Device Interface */ +static struct inotify_inode_mark *inotify_idr_find(struct fsnotify_group *group, + int wd) +{ + struct inotify_inode_mark *i_mark; + spinlock_t *idr_lock = &group->inotify_data.idr_lock; -static unsigned int inotify_poll(struct file *file, poll_table *wait) + spin_lock(idr_lock); + i_mark = inotify_idr_find_locked(group, wd); + spin_unlock(idr_lock); + + return i_mark; +} + +static void do_inotify_remove_from_idr(struct fsnotify_group *group, + struct inotify_inode_mark *i_mark) { - struct inotify_device *dev = file->private_data; - int ret = 0; + struct idr *idr = &group->inotify_data.idr; + spinlock_t *idr_lock = &group->inotify_data.idr_lock; + int wd = i_mark->wd; - poll_wait(file, &dev->wq, wait); - mutex_lock(&dev->ev_mutex); - if (!list_empty(&dev->events)) - ret = POLLIN | POLLRDNORM; - mutex_unlock(&dev->ev_mutex); + assert_spin_locked(idr_lock); - return ret; + idr_remove(idr, wd); + + /* removed from the idr, drop that ref */ + fsnotify_put_mark(&i_mark->fsn_mark); } /* - * Get an inotify_kernel_event if one exists and is small - * enough to fit in "count". Return an error pointer if - * not large enough. - * - * Called with the device ev_mutex held. + * Remove the mark from the idr (if present) and drop the reference + * on the mark because it was in the idr. */ -static struct inotify_kernel_event *get_one_event(struct inotify_device *dev, - size_t count) +static void inotify_remove_from_idr(struct fsnotify_group *group, + struct inotify_inode_mark *i_mark) { - size_t event_size = sizeof(struct inotify_event); - struct inotify_kernel_event *kevent; + spinlock_t *idr_lock = &group->inotify_data.idr_lock; + struct inotify_inode_mark *found_i_mark = NULL; + int wd; - if (list_empty(&dev->events)) - return NULL; + spin_lock(idr_lock); + wd = i_mark->wd; - kevent = inotify_dev_get_event(dev); - if (kevent->name) - event_size += kevent->event.len; + /* + * does this i_mark think it is in the idr? we shouldn't get called + * if it wasn't.... + */ + if (wd == -1) { + WARN_ONCE(1, "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p" + " i_mark->inode=%p\n", __func__, i_mark, i_mark->wd, + i_mark->fsn_mark.group, i_mark->fsn_mark.i.inode); + goto out; + } - if (event_size > count) - return ERR_PTR(-EINVAL); + /* Lets look in the idr to see if we find it */ + found_i_mark = inotify_idr_find_locked(group, wd); + if (unlikely(!found_i_mark)) { + WARN_ONCE(1, "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p" + " i_mark->inode=%p\n", __func__, i_mark, i_mark->wd, + i_mark->fsn_mark.group, i_mark->fsn_mark.i.inode); + goto out; + } - remove_kevent(dev, kevent); - return kevent; + /* + * We found an mark in the idr at the right wd, but it's + * not the mark we were told to remove. eparis seriously + * fucked up somewhere. + */ + if (unlikely(found_i_mark != i_mark)) { + WARN_ONCE(1, "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p " + "mark->inode=%p found_i_mark=%p found_i_mark->wd=%d " + "found_i_mark->group=%p found_i_mark->inode=%p\n", + __func__, i_mark, i_mark->wd, i_mark->fsn_mark.group, + i_mark->fsn_mark.i.inode, found_i_mark, found_i_mark->wd, + found_i_mark->fsn_mark.group, + found_i_mark->fsn_mark.i.inode); + goto out; + } + + /* + * One ref for being in the idr + * one ref held by the caller trying to kill us + * one ref grabbed by inotify_idr_find + */ + if (unlikely(atomic_read(&i_mark->fsn_mark.refcnt) < 3)) { + printk(KERN_ERR "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p" + " i_mark->inode=%p\n", __func__, i_mark, i_mark->wd, + i_mark->fsn_mark.group, i_mark->fsn_mark.i.inode); + /* we can't really recover with bad ref cnting.. */ + BUG(); + } + + do_inotify_remove_from_idr(group, i_mark); +out: + /* match the ref taken by inotify_idr_find_locked() */ + if (found_i_mark) + fsnotify_put_mark(&found_i_mark->fsn_mark); + i_mark->wd = -1; + spin_unlock(idr_lock); } /* - * Copy an event to user space, returning how much we copied. - * - * We already checked that the event size is smaller than the - * buffer we had in "get_one_event()" above. + * Send IN_IGNORED for this wd, remove this wd from the idr. */ -static ssize_t copy_event_to_user(struct inotify_kernel_event *kevent, - char __user *buf) +void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark, + struct fsnotify_group *group) { - size_t event_size = sizeof(struct inotify_event); + struct inotify_inode_mark *i_mark; - if (copy_to_user(buf, &kevent->event, event_size)) - return -EFAULT; + /* Queue ignore event for the watch */ + inotify_handle_event(group, NULL, fsn_mark, NULL, FS_IN_IGNORED, + NULL, FSNOTIFY_EVENT_NONE, NULL, 0); - if (kevent->name) { - buf += event_size; + i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark); + /* remove this mark from the idr */ + inotify_remove_from_idr(group, i_mark); - if (copy_to_user(buf, kevent->name, kevent->event.len)) - return -EFAULT; + atomic_dec(&group->inotify_data.user->inotify_watches); +} - event_size += kevent->event.len; - } - return event_size; +/* ding dong the mark is dead */ +static void inotify_free_mark(struct fsnotify_mark *fsn_mark) +{ + struct inotify_inode_mark *i_mark; + + i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark); + + kmem_cache_free(inotify_inode_mark_cachep, i_mark); } -static ssize_t inotify_read(struct file *file, char __user *buf, - size_t count, loff_t *pos) +static int inotify_update_existing_watch(struct fsnotify_group *group, + struct inode *inode, + u32 arg) { - struct inotify_device *dev; - char __user *start; + struct fsnotify_mark *fsn_mark; + struct inotify_inode_mark *i_mark; + __u32 old_mask, new_mask; + __u32 mask; + int add = (arg & IN_MASK_ADD); int ret; - DEFINE_WAIT(wait); - start = buf; - dev = file->private_data; + mask = inotify_arg_to_mask(arg); - while (1) { - struct inotify_kernel_event *kevent; + fsn_mark = fsnotify_find_inode_mark(group, inode); + if (!fsn_mark) + return -ENOENT; - prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE); + i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark); - mutex_lock(&dev->ev_mutex); - kevent = get_one_event(dev, count); - mutex_unlock(&dev->ev_mutex); + spin_lock(&fsn_mark->lock); - if (kevent) { - ret = PTR_ERR(kevent); - if (IS_ERR(kevent)) - break; - ret = copy_event_to_user(kevent, buf); - free_kevent(kevent); - if (ret < 0) - break; - buf += ret; - count -= ret; - continue; - } + old_mask = fsn_mark->mask; + if (add) + fsnotify_set_mark_mask_locked(fsn_mark, (fsn_mark->mask | mask)); + else + fsnotify_set_mark_mask_locked(fsn_mark, mask); + new_mask = fsn_mark->mask; - ret = -EAGAIN; - if (file->f_flags & O_NONBLOCK) - break; - ret = -EINTR; - if (signal_pending(current)) - break; + spin_unlock(&fsn_mark->lock); - if (start != buf) - break; + if (old_mask != new_mask) { + /* more bits in old than in new? */ + int dropped = (old_mask & ~new_mask); + /* more bits in this fsn_mark than the inode's mask? */ + int do_inode = (new_mask & ~inode->i_fsnotify_mask); + + /* update the inode with this new fsn_mark */ + if (dropped || do_inode) + fsnotify_recalc_inode_mask(inode); - schedule(); } - finish_wait(&dev->wq, &wait); - if (start != buf && ret != -EFAULT) - ret = buf - start; + /* return the wd */ + ret = i_mark->wd; + + /* match the get from fsnotify_find_mark() */ + fsnotify_put_mark(fsn_mark); + return ret; } -static int inotify_fasync(int fd, struct file *file, int on) +static int inotify_new_watch(struct fsnotify_group *group, + struct inode *inode, + u32 arg) { - struct inotify_device *dev = file->private_data; + struct inotify_inode_mark *tmp_i_mark; + __u32 mask; + int ret; + struct idr *idr = &group->inotify_data.idr; + spinlock_t *idr_lock = &group->inotify_data.idr_lock; - return fasync_helper(fd, file, on, &dev->fa) >= 0 ? 0 : -EIO; -} + mask = inotify_arg_to_mask(arg); -static int inotify_release(struct inode *ignored, struct file *file) -{ - struct inotify_device *dev = file->private_data; + tmp_i_mark = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL); + if (unlikely(!tmp_i_mark)) + return -ENOMEM; - inotify_destroy(dev->ih); + fsnotify_init_mark(&tmp_i_mark->fsn_mark, inotify_free_mark); + tmp_i_mark->fsn_mark.mask = mask; + tmp_i_mark->wd = -1; + + ret = -ENOSPC; + if (atomic_read(&group->inotify_data.user->inotify_watches) >= inotify_max_user_watches) + goto out_err; + + ret = inotify_add_to_idr(idr, idr_lock, tmp_i_mark); + if (ret) + goto out_err; + + /* we are on the idr, now get on the inode */ + ret = fsnotify_add_mark_locked(&tmp_i_mark->fsn_mark, group, inode, + NULL, 0); + if (ret) { + /* we failed to get on the inode, get off the idr */ + inotify_remove_from_idr(group, tmp_i_mark); + goto out_err; + } - /* destroy all of the events on this device */ - mutex_lock(&dev->ev_mutex); - while (!list_empty(&dev->events)) - inotify_dev_event_dequeue(dev); - mutex_unlock(&dev->ev_mutex); + /* increment the number of watches the user has */ + atomic_inc(&group->inotify_data.user->inotify_watches); - /* free this device: the put matching the get in inotify_init() */ - put_inotify_dev(dev); + /* return the watch descriptor for this new mark */ + ret = tmp_i_mark->wd; - return 0; +out_err: + /* match the ref from fsnotify_init_mark() */ + fsnotify_put_mark(&tmp_i_mark->fsn_mark); + + return ret; } -static long inotify_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static int inotify_update_watch(struct fsnotify_group *group, struct inode *inode, u32 arg) { - struct inotify_device *dev; - void __user *p; - int ret = -ENOTTY; - - dev = file->private_data; - p = (void __user *) arg; + int ret = 0; - switch (cmd) { - case FIONREAD: - ret = put_user(dev->queue_size, (int __user *) p); - break; - } + mutex_lock(&group->mark_mutex); + /* try to update and existing watch with the new arg */ + ret = inotify_update_existing_watch(group, inode, arg); + /* no mark present, try to add a new one */ + if (ret == -ENOENT) + ret = inotify_new_watch(group, inode, arg); + mutex_unlock(&group->mark_mutex); return ret; } -static const struct file_operations inotify_fops = { - .poll = inotify_poll, - .read = inotify_read, - .fasync = inotify_fasync, - .release = inotify_release, - .unlocked_ioctl = inotify_ioctl, - .compat_ioctl = inotify_ioctl, -}; +static struct fsnotify_group *inotify_new_group(unsigned int max_events) +{ + struct fsnotify_group *group; + struct inotify_event_info *oevent; + + group = fsnotify_alloc_group(&inotify_fsnotify_ops); + if (IS_ERR(group)) + return group; + + oevent = kmalloc(sizeof(struct inotify_event_info), GFP_KERNEL); + if (unlikely(!oevent)) { + fsnotify_destroy_group(group); + return ERR_PTR(-ENOMEM); + } + group->overflow_event = &oevent->fse; + fsnotify_init_event(group->overflow_event, NULL, FS_Q_OVERFLOW); + oevent->wd = -1; + oevent->sync_cookie = 0; + oevent->name_len = 0; + + group->max_events = max_events; + + spin_lock_init(&group->inotify_data.idr_lock); + idr_init(&group->inotify_data.idr); + group->inotify_data.user = get_current_user(); + + if (atomic_inc_return(&group->inotify_data.user->inotify_devs) > + inotify_max_user_instances) { + fsnotify_destroy_group(group); + return ERR_PTR(-EMFILE); + } + + return group; +} -static const struct inotify_operations inotify_user_ops = { - .handle_event = inotify_dev_queue_event, - .destroy_watch = free_inotify_user_watch, -}; +/* inotify syscalls */ SYSCALL_DEFINE1(inotify_init1, int, flags) { - struct inotify_device *dev; - struct inotify_handle *ih; - struct user_struct *user; - struct file *filp; - int fd, ret; + struct fsnotify_group *group; + int ret; /* Check the IN_* constants for consistency. */ BUILD_BUG_ON(IN_CLOEXEC != O_CLOEXEC); @@ -604,67 +679,16 @@ SYSCALL_DEFINE1(inotify_init1, int, flags) if (flags & ~(IN_CLOEXEC | IN_NONBLOCK)) return -EINVAL; - fd = get_unused_fd_flags(flags & O_CLOEXEC); - if (fd < 0) - return fd; - - filp = get_empty_filp(); - if (!filp) { - ret = -ENFILE; - goto out_put_fd; - } + /* fsnotify_obtain_group took a reference to group, we put this when we kill the file in the end */ + group = inotify_new_group(inotify_max_queued_events); + if (IS_ERR(group)) + return PTR_ERR(group); - user = get_current_user(); - if (unlikely(atomic_read(&user->inotify_devs) >= - inotify_max_user_instances)) { - ret = -EMFILE; - goto out_free_uid; - } - - dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL); - if (unlikely(!dev)) { - ret = -ENOMEM; - goto out_free_uid; - } + ret = anon_inode_getfd("inotify", &inotify_fops, group, + O_RDONLY | flags); + if (ret < 0) + fsnotify_destroy_group(group); - ih = inotify_init(&inotify_user_ops); - if (IS_ERR(ih)) { - ret = PTR_ERR(ih); - goto out_free_dev; - } - dev->ih = ih; - dev->fa = NULL; - - filp->f_op = &inotify_fops; - filp->f_path.mnt = mntget(inotify_mnt); - filp->f_path.dentry = dget(inotify_mnt->mnt_root); - filp->f_mapping = filp->f_path.dentry->d_inode->i_mapping; - filp->f_mode = FMODE_READ; - filp->f_flags = O_RDONLY | (flags & O_NONBLOCK); - filp->private_data = dev; - - INIT_LIST_HEAD(&dev->events); - init_waitqueue_head(&dev->wq); - mutex_init(&dev->ev_mutex); - mutex_init(&dev->up_mutex); - dev->event_count = 0; - dev->queue_size = 0; - dev->max_events = inotify_max_queued_events; - dev->user = user; - atomic_set(&dev->count, 0); - - get_inotify_dev(dev); - atomic_inc(&user->inotify_devs); - fd_install(fd, filp); - - return fd; -out_free_dev: - kfree(dev); -out_free_uid: - free_uid(user); - put_filp(filp); -out_put_fd: - put_unused_fd(fd); return ret; } @@ -676,19 +700,23 @@ SYSCALL_DEFINE0(inotify_init) SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname, u32, mask) { + struct fsnotify_group *group; struct inode *inode; - struct inotify_device *dev; struct path path; - struct file *filp; - int ret, fput_needed; + struct fd f; + int ret; unsigned flags = 0; - filp = fget_light(fd, &fput_needed); - if (unlikely(!filp)) + /* don't allow invalid bits: we don't want flags set */ + if (unlikely(!(mask & ALL_INOTIFY_BITS))) + return -EINVAL; + + f = fdget(fd); + if (unlikely(!f.file)) return -EBADF; /* verify that this is indeed an inotify instance */ - if (unlikely(filp->f_op != &inotify_fops)) { + if (unlikely(f.file->f_op != &inotify_fops)) { ret = -EINVAL; goto fput_and_out; } @@ -698,95 +726,91 @@ SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname, if (mask & IN_ONLYDIR) flags |= LOOKUP_DIRECTORY; - ret = find_inode(pathname, &path, flags); - if (unlikely(ret)) + ret = inotify_find_inode(pathname, &path, flags); + if (ret) goto fput_and_out; - /* inode held in place by reference to path; dev by fget on fd */ + /* inode held in place by reference to path; group by fget on fd */ inode = path.dentry->d_inode; - dev = filp->private_data; - - mutex_lock(&dev->up_mutex); - ret = inotify_find_update_watch(dev->ih, inode, mask); - if (ret == -ENOENT) - ret = create_watch(dev, inode, mask); - mutex_unlock(&dev->up_mutex); + group = f.file->private_data; + /* create/update an inode mark */ + ret = inotify_update_watch(group, inode, mask); path_put(&path); fput_and_out: - fput_light(filp, fput_needed); + fdput(f); return ret; } SYSCALL_DEFINE2(inotify_rm_watch, int, fd, __s32, wd) { - struct file *filp; - struct inotify_device *dev; - int ret, fput_needed; + struct fsnotify_group *group; + struct inotify_inode_mark *i_mark; + struct fd f; + int ret = 0; - filp = fget_light(fd, &fput_needed); - if (unlikely(!filp)) + f = fdget(fd); + if (unlikely(!f.file)) return -EBADF; /* verify that this is indeed an inotify instance */ - if (unlikely(filp->f_op != &inotify_fops)) { - ret = -EINVAL; + ret = -EINVAL; + if (unlikely(f.file->f_op != &inotify_fops)) goto out; - } - dev = filp->private_data; + group = f.file->private_data; - /* we free our watch data when we get IN_IGNORED */ - ret = inotify_rm_wd(dev->ih, wd); + ret = -EINVAL; + i_mark = inotify_idr_find(group, wd); + if (unlikely(!i_mark)) + goto out; + + ret = 0; + + fsnotify_destroy_mark(&i_mark->fsn_mark, group); + + /* match ref taken by inotify_idr_find */ + fsnotify_put_mark(&i_mark->fsn_mark); out: - fput_light(filp, fput_needed); + fdput(f); return ret; } -static int -inotify_get_sb(struct file_system_type *fs_type, int flags, - const char *dev_name, void *data, struct vfsmount *mnt) -{ - return get_sb_pseudo(fs_type, "inotify", NULL, - INOTIFYFS_SUPER_MAGIC, mnt); -} - -static struct file_system_type inotify_fs_type = { - .name = "inotifyfs", - .get_sb = inotify_get_sb, - .kill_sb = kill_anon_super, -}; - /* - * inotify_user_setup - Our initialization function. Note that we cannnot return + * inotify_user_setup - Our initialization function. Note that we cannot return * error because we have compiled-in VFS hooks. So an (unlikely) failure here * must result in panic(). */ static int __init inotify_user_setup(void) { - int ret; - - ret = register_filesystem(&inotify_fs_type); - if (unlikely(ret)) - panic("inotify: register_filesystem returned %d!\n", ret); - - inotify_mnt = kern_mount(&inotify_fs_type); - if (IS_ERR(inotify_mnt)) - panic("inotify: kern_mount ret %ld!\n", PTR_ERR(inotify_mnt)); + BUILD_BUG_ON(IN_ACCESS != FS_ACCESS); + BUILD_BUG_ON(IN_MODIFY != FS_MODIFY); + BUILD_BUG_ON(IN_ATTRIB != FS_ATTRIB); + BUILD_BUG_ON(IN_CLOSE_WRITE != FS_CLOSE_WRITE); + BUILD_BUG_ON(IN_CLOSE_NOWRITE != FS_CLOSE_NOWRITE); + BUILD_BUG_ON(IN_OPEN != FS_OPEN); + BUILD_BUG_ON(IN_MOVED_FROM != FS_MOVED_FROM); + BUILD_BUG_ON(IN_MOVED_TO != FS_MOVED_TO); + BUILD_BUG_ON(IN_CREATE != FS_CREATE); + BUILD_BUG_ON(IN_DELETE != FS_DELETE); + BUILD_BUG_ON(IN_DELETE_SELF != FS_DELETE_SELF); + BUILD_BUG_ON(IN_MOVE_SELF != FS_MOVE_SELF); + BUILD_BUG_ON(IN_UNMOUNT != FS_UNMOUNT); + BUILD_BUG_ON(IN_Q_OVERFLOW != FS_Q_OVERFLOW); + BUILD_BUG_ON(IN_IGNORED != FS_IN_IGNORED); + BUILD_BUG_ON(IN_EXCL_UNLINK != FS_EXCL_UNLINK); + BUILD_BUG_ON(IN_ISDIR != FS_ISDIR); + BUILD_BUG_ON(IN_ONESHOT != FS_IN_ONESHOT); + + BUG_ON(hweight32(ALL_INOTIFY_BITS) != 21); + + inotify_inode_mark_cachep = KMEM_CACHE(inotify_inode_mark, SLAB_PANIC); inotify_max_queued_events = 16384; inotify_max_user_instances = 128; inotify_max_user_watches = 8192; - watch_cachep = kmem_cache_create("inotify_watch_cache", - sizeof(struct inotify_user_watch), - 0, SLAB_PANIC, NULL); - event_cachep = kmem_cache_create("inotify_event_cache", - sizeof(struct inotify_kernel_event), - 0, SLAB_PANIC, NULL); - return 0; } - module_init(inotify_user_setup); |
