diff options
author | Thomas Gleixner <tglx@tglx.tec.linutronix.de> | 2005-07-13 10:45:00 +0200 |
---|---|---|
committer | Thomas Gleixner <tglx@mtd.linutronix.de> | 2005-07-13 10:45:00 +0200 |
commit | 1b3035b7fcc72b6b36f2a3634dad832eb2453ce8 (patch) | |
tree | 728174d72943cc9b981a5843553551df62eddc41 /fs | |
parent | a98a5d04f400ad112e59cadd739dbabf89417e60 (diff) | |
parent | c32511e2718618f0b53479eb36e07439aa363a74 (diff) |
Merge with rsync://fileserver/linux
Diffstat (limited to 'fs')
43 files changed, 24413 insertions, 21521 deletions
diff --git a/fs/Kconfig b/fs/Kconfig index f93fd41b025..5d0c4be43db 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -359,6 +359,19 @@ config ROMFS_FS If you don't know whether you need it, then you don't need it: answer N. +config INOTIFY + bool "Inotify file change notification support" + default y + ---help--- + Say Y here to enable inotify support and the /dev/inotify character + device. Inotify is a file change notification system and a + replacement for dnotify. Inotify fixes numerous shortcomings in + dnotify and introduces several new features. It allows monitoring + of both files and directories via a single open fd. Multiple file + events are supported. + + If unsure, say Y. + config QUOTA bool "Quota support" help diff --git a/fs/Makefile b/fs/Makefile index 20edcf28bfd..cf95eb894fd 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -12,6 +12,7 @@ obj-y := open.o read_write.o file_table.o buffer.o bio.o super.o \ seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \ ioprio.o +obj-$(CONFIG_INOTIFY) += inotify.o obj-$(CONFIG_EPOLL) += eventpoll.o obj-$(CONFIG_COMPAT) += compat.o diff --git a/fs/attr.c b/fs/attr.c index c3c76fe7834..b1796fb9e52 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -10,7 +10,7 @@ #include <linux/mm.h> #include <linux/string.h> #include <linux/smp_lock.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/fcntl.h> #include <linux/quotaops.h> #include <linux/security.h> @@ -107,31 +107,8 @@ int inode_setattr(struct inode * inode, struct iattr * attr) out: return error; } - EXPORT_SYMBOL(inode_setattr); -int setattr_mask(unsigned int ia_valid) -{ - unsigned long dn_mask = 0; - - if (ia_valid & ATTR_UID) - dn_mask |= DN_ATTRIB; - if (ia_valid & ATTR_GID) - dn_mask |= DN_ATTRIB; - if (ia_valid & ATTR_SIZE) - dn_mask |= DN_MODIFY; - /* both times implies a utime(s) call */ - if ((ia_valid & (ATTR_ATIME|ATTR_MTIME)) == (ATTR_ATIME|ATTR_MTIME)) - dn_mask |= DN_ATTRIB; - else if (ia_valid & ATTR_ATIME) - dn_mask |= DN_ACCESS; - else if (ia_valid & ATTR_MTIME) - dn_mask |= DN_MODIFY; - if (ia_valid & ATTR_MODE) - dn_mask |= DN_ATTRIB; - return dn_mask; -} - int notify_change(struct dentry * dentry, struct iattr * attr) { struct inode *inode = dentry->d_inode; @@ -197,11 +174,9 @@ int notify_change(struct dentry * dentry, struct iattr * attr) if (ia_valid & ATTR_SIZE) up_write(&dentry->d_inode->i_alloc_sem); - if (!error) { - unsigned long dn_mask = setattr_mask(ia_valid); - if (dn_mask) - dnotify_parent(dentry, dn_mask); - } + if (!error) + fsnotify_change(dentry, ia_valid); + return error; } diff --git a/fs/char_dev.c b/fs/char_dev.c index a69a5d8a406..3b1b1eefdbb 100644 --- a/fs/char_dev.c +++ b/fs/char_dev.c @@ -277,8 +277,9 @@ static struct kobject *cdev_get(struct cdev *p) void cdev_put(struct cdev *p) { if (p) { + struct module *owner = p->owner; kobject_put(&p->kobj); - module_put(p->owner); + module_put(owner); } } diff --git a/fs/compat.c b/fs/compat.c index 728cd836538..6b06b6bae35 100644 --- a/fs/compat.c +++ b/fs/compat.c @@ -37,7 +37,7 @@ #include <linux/ctype.h> #include <linux/module.h> #include <linux/dirent.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/highuid.h> #include <linux/sunrpc/svc.h> #include <linux/nfsd/nfsd.h> @@ -1307,9 +1307,13 @@ static ssize_t compat_do_readv_writev(int type, struct file *file, out: if (iov != iovstack) kfree(iov); - if ((ret + (type == READ)) > 0) - dnotify_parent(file->f_dentry, - (type == READ) ? DN_ACCESS : DN_MODIFY); + if ((ret + (type == READ)) > 0) { + struct dentry *dentry = file->f_dentry; + if (type == READ) + fsnotify_access(dentry); + else + fsnotify_modify(dentry); + } return ret; } diff --git a/fs/exec.c b/fs/exec.c index 48871917d36..222ab1c572d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -642,6 +642,18 @@ static inline int de_thread(struct task_struct *tsk) count = 2; if (thread_group_leader(current)) count = 1; + else { + /* + * The SIGALRM timer survives the exec, but needs to point + * at us as the new group leader now. We have a race with + * a timer firing now getting the old leader, so we need to + * synchronize with any firing (by calling del_timer_sync) + * before we can safely let the old group leader die. + */ + sig->real_timer.data = (unsigned long)current; + if (del_timer_sync(&sig->real_timer)) + add_timer(&sig->real_timer); + } while (atomic_read(&sig->count) > count) { sig->group_exit_task = current; sig->notify_count = count; diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index eed521d22cf..e977f8566d1 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -2,6 +2,15 @@ #include <linux/ext2_fs.h> /* + * ext2 mount options + */ +struct ext2_mount_options { + unsigned long s_mount_opt; + uid_t s_resuid; + gid_t s_resgid; +}; + +/* * second extended file system inode data in memory */ struct ext2_inode_info { diff --git a/fs/ext2/super.c b/fs/ext2/super.c index 876e391f287..dcfe331dc4c 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -936,12 +936,23 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data) struct ext2_sb_info * sbi = EXT2_SB(sb); struct ext2_super_block * es; unsigned long old_mount_opt = sbi->s_mount_opt; + struct ext2_mount_options old_opts; + unsigned long old_sb_flags; + int err; + + /* Store the old options */ + old_sb_flags = sb->s_flags; + old_opts.s_mount_opt = sbi->s_mount_opt; + old_opts.s_resuid = sbi->s_resuid; + old_opts.s_resgid = sbi->s_resgid; /* * Allow the "check" option to be passed as a remount option. */ - if (!parse_options (data, sbi)) - return -EINVAL; + if (!parse_options (data, sbi)) { + err = -EINVAL; + goto restore_opts; + } sb->s_flags = (sb->s_flags & ~MS_POSIXACL) | ((sbi->s_mount_opt & EXT2_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0); @@ -971,7 +982,8 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data) printk("EXT2-fs: %s: couldn't remount RDWR because of " "unsupported optional features (%x).\n", sb->s_id, le32_to_cpu(ret)); - return -EROFS; + err = -EROFS; + goto restore_opts; } /* * Mounting a RDONLY partition read-write, so reread and @@ -984,6 +996,12 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data) } ext2_sync_super(sb, es); return 0; +restore_opts: + sbi->s_mount_opt = old_opts.s_mount_opt; + sbi->s_resuid = old_opts.s_resuid; + sbi->s_resgid = old_opts.s_resgid; + sb->s_flags = old_sb_flags; + return err; } static int ext2_statfs (struct super_block * sb, struct kstatfs * buf) diff --git a/fs/ext3/super.c b/fs/ext3/super.c index a6d1779d7de..3c3c6e399fb 100644 --- a/fs/ext3/super.c +++ b/fs/ext3/super.c @@ -890,7 +890,10 @@ clear_qf_name: "quota turned on.\n"); return 0; } - kfree(sbi->s_qf_names[qtype]); + /* + * The space will be released later when all options + * are confirmed to be correct + */ sbi->s_qf_names[qtype] = NULL; break; case Opt_jqfmt_vfsold: @@ -939,7 +942,7 @@ clear_qf_name: case Opt_ignore: break; case Opt_resize: - if (!n_blocks_count) { + if (!is_remount) { printk("EXT3-fs: resize option only available " "for remount\n"); return 0; @@ -2109,14 +2112,33 @@ static int ext3_remount (struct super_block * sb, int * flags, char * data) { struct ext3_super_block * es; struct ext3_sb_info *sbi = EXT3_SB(sb); - unsigned long tmp; unsigned long n_blocks_count = 0; + unsigned long old_sb_flags; + struct ext3_mount_options old_opts; + int err; +#ifdef CONFIG_QUOTA + int i; +#endif + + /* Store the original options */ + old_sb_flags = sb->s_flags; + old_opts.s_mount_opt = sbi->s_mount_opt; + old_opts.s_resuid = sbi->s_resuid; + old_opts.s_resgid = sbi->s_resgid; + old_opts.s_commit_interval = sbi->s_commit_interval; +#ifdef CONFIG_QUOTA + old_opts.s_jquota_fmt = sbi->s_jquota_fmt; + for (i = 0; i < MAXQUOTAS; i++) + old_opts.s_qf_names[i] = sbi->s_qf_names[i]; +#endif /* * Allow the "check" option to be passed as a remount option. */ - if (!parse_options(data, sb, &tmp, &n_blocks_count, 1)) - return -EINVAL; + if (!parse_options(data, sb, NULL, &n_blocks_count, 1)) { + err = -EINVAL; + goto restore_opts; + } if (sbi->s_mount_opt & EXT3_MOUNT_ABORT) ext3_abort(sb, __FUNCTION__, "Abort forced by user"); @@ -2130,8 +2152,10 @@ static int ext3_remount (struct super_block * sb, int * flags, char * data) if ((*flags & MS_RDONLY) != (sb->s_flags & MS_RDONLY) || n_blocks_count > le32_to_cpu(es->s_blocks_count)) { - if (sbi->s_mount_opt & EXT3_MOUNT_ABORT) - return -EROFS; + if (sbi->s_mount_opt & EXT3_MOUNT_ABORT) { + err = -EROFS; + goto restore_opts; + } if (*flags & MS_RDONLY) { /* @@ -2158,7 +2182,8 @@ static int ext3_remount (struct super_block * sb, int * flags, char * data) "remount RDWR because of unsupported " "optional features (%x).\n", sb->s_id, le32_to_cpu(ret)); - return -EROFS; + err = -EROFS; + goto restore_opts; } /* * Mounting a RDONLY partition read-write, so reread @@ -2168,13 +2193,38 @@ static int ext3_remount (struct super_block * sb, int * flags, char * data) */ ext3_clear_journal_err(sb, es); sbi->s_mount_state = le16_to_cpu(es->s_state); - if ((ret = ext3_group_extend(sb, es, n_blocks_count))) - return ret; + if ((ret = ext3_group_extend(sb, es, n_blocks_count))) { + err = ret; + goto restore_opts; + } if (!ext3_setup_super (sb, es, 0)) sb->s_flags &= ~MS_RDONLY; } } +#ifdef CONFIG_QUOTA + /* Release old quota file names */ + for (i = 0; i < MAXQUOTAS; i++) + if (old_opts.s_qf_names[i] && + old_opts.s_qf_names[i] != sbi->s_qf_names[i]) + kfree(old_opts.s_qf_names[i]); +#endif return 0; +restore_opts: + sb->s_flags = old_sb_flags; + sbi->s_mount_opt = old_opts.s_mount_opt; + sbi->s_resuid = old_opts.s_resuid; + sbi->s_resgid = old_opts.s_resgid; + sbi->s_commit_interval = old_opts.s_commit_interval; +#ifdef CONFIG_QUOTA + sbi->s_jquota_fmt = old_opts.s_jquota_fmt; + for (i = 0; i < MAXQUOTAS; i++) { + if (sbi->s_qf_names[i] && + old_opts.s_qf_names[i] != sbi->s_qf_names[i]) + kfree(sbi->s_qf_names[i]); + sbi->s_qf_names[i] = old_opts.s_qf_names[i]; + } +#endif + return err; } static int ext3_statfs (struct super_block * sb, struct kstatfs * buf) diff --git a/fs/file_table.c b/fs/file_table.c index fa7849fae13..1d3de78e6bc 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -16,6 +16,7 @@ #include <linux/eventpoll.h> #include <linux/mount.h> #include <linux/cdev.h> +#include <linux/fsnotify.h> /* sysctl tunables... */ struct files_stat_struct files_stat = { @@ -126,6 +127,8 @@ void fastcall __fput(struct file *file) struct inode *inode = dentry->d_inode; might_sleep(); + + fsnotify_close(file); /* * The function eventpoll_release() should be the first called * in the file cleanup chain. diff --git a/fs/inode.c b/fs/inode.c index 6d695037a0a..96364fae084 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -21,6 +21,7 @@ #include <linux/pagemap.h> #include <linux/cdev.h> #include <linux/bootmem.h> +#include <linux/inotify.h> /* * This is needed for the following functions: @@ -202,6 +203,10 @@ void inode_init_once(struct inode *inode) INIT_LIST_HEAD(&inode->i_data.i_mmap_nonlinear); spin_lock_init(&inode->i_lock); i_size_ordered_init(inode); +#ifdef CONFIG_INOTIFY + INIT_LIST_HEAD(&inode->inotify_watches); + sema_init(&inode->inotify_sem, 1); +#endif } EXPORT_SYMBOL(inode_init_once); @@ -282,6 +287,13 @@ static void dispose_list(struct list_head *head) if (inode->i_data.nrpages) truncate_inode_pages(&inode->i_data, 0); clear_inode(inode); + + spin_lock(&inode_lock); + hlist_del_init(&inode->i_hash); + list_del_init(&inode->i_sb_list); + spin_unlock(&inode_lock); + + wake_up_inode(inode); destroy_inode(inode); nr_disposed++; } @@ -317,8 +329,6 @@ static int invalidate_list(struct list_head *head, struct list_head *dispose) inode = list_entry(tmp, struct inode, i_sb_list); invalidate_inode_buffers(inode); if (!atomic_read(&inode->i_count)) { - hlist_del_init(&inode->i_hash); - list_del(&inode->i_sb_list); list_move(&inode->i_list, dispose); inode->i_state |= I_FREEING; count++; @@ -346,6 +356,7 @@ int invalidate_inodes(struct super_block * sb) down(&iprune_sem); spin_lock(&inode_lock); + inotify_unmount_inodes(&sb->s_inodes); busy = invalidate_list(&sb->s_inodes, &throw_away); spin_unlock(&inode_lock); @@ -439,8 +450,6 @@ static void prune_icache(int nr_to_scan) if (!can_unuse(inode)) continue; } - hlist_del_init(&inode->i_hash); - list_del_init(&inode->i_sb_list); list_move(&inode->i_list, &freeable); inode->i_state |= I_FREEING; nr_pruned++; @@ -1244,29 +1253,21 @@ int inode_wait(void *word) } /* - * If we try to find an inode in the inode hash while it is being deleted, we - * have to wait until the filesystem completes its deletion before reporting - * that it isn't found. This is because iget will immediately call - * ->read_inode, and we want to be sure that evidence of the deletion is found - * by ->read_inode. + * If we try to find an inode in the inode hash while it is being + * deleted, we have to wait until the filesystem completes its + * deletion before reporting that it isn't found. This function waits + * until the deletion _might_ have completed. Callers are responsible + * to recheck inode state. + * + * It doesn't matter if I_LOCK is not set initially, a call to + * wake_up_inode() after removing from the hash list will DTRT. + * * This is called with inode_lock held. */ static void __wait_on_freeing_inode(struct inode *inode) { wait_queue_head_t *wq; DEFINE_WAIT_BIT(wait, &inode->i_state, __I_LOCK); - - /* - * I_FREEING and I_CLEAR are cleared in process context under - * inode_lock, so we have to give the tasks who would clear them - * a chance to run and acquire inode_lock. - */ - if (!(inode->i_state & I_LOCK)) { - spin_unlock(&inode_lock); - yield(); - spin_lock(&inode_lock); - return; - } wq = bit_waitqueue(&inode->i_state, __I_LOCK); prepare_to_wait(wq, &wait.wait, TASK_UNINTERRUPTIBLE); spin_unlock(&inode_lock); diff --git a/fs/inotify.c b/fs/inotify.c new file mode 100644 index 00000000000..e423bfe0c86 --- /dev/null +++ b/fs/inotify.c @@ -0,0 +1,999 @@ +/* + * fs/inotify.c - inode-based file event notifications + * + * Authors: + * John McCutchan <ttb@tentacle.dhs.org> + * Robert Love <rml@novell.com> + * + * Copyright (C) 2005 John McCutchan + * + * 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 + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/idr.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/device.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/writeback.h> +#include <linux/inotify.h> + +#include <asm/ioctls.h> + +static atomic_t inotify_cookie; + +static kmem_cache_t *watch_cachep; +static kmem_cache_t *event_cachep; + +static struct vfsmount *inotify_mnt; + +/* These are configurable via /proc/sys/inotify */ +int inotify_max_user_devices; +int inotify_max_user_watches; +int inotify_max_queued_events; + +/* + * Lock ordering: + * + * dentry->d_lock (used to keep d_move() away from dentry->d_parent) + * iprune_sem (synchronize shrink_icache_memory()) + * inode_lock (protects the super_block->s_inodes list) + * inode->inotify_sem (protects inode->inotify_watches and watches->i_list) + * inotify_dev->sem (protects inotify_device and watches->d_list) + */ + +/* + * Lifetimes of the three main data structures--inotify_device, inode, and + * inotify_watch--are managed by reference count. + * + * inotify_device: Lifetime is from open until release. Additional references + * can bump the count via get_inotify_dev() and drop the count via + * put_inotify_dev(). + * + * inotify_watch: Lifetime is from create_watch() to destory_watch(). + * Additional references can bump the count via get_inotify_watch() and drop + * the count via put_inotify_watch(). + * + * inode: Pinned so long as the inode is associated with a watch, from + * create_watch() to put_inotify_watch(). + */ + +/* + * struct inotify_device - represents an open instance of an inotify device + * + * This structure is protected by the semaphore 'sem'. + */ +struct inotify_device { + wait_queue_head_t wq; /* wait queue for i/o */ + struct idr idr; /* idr mapping wd -> watch */ + struct semaphore sem; /* protects this bad boy */ + struct list_head events; /* list of queued events */ + struct list_head watches; /* list of watches */ + atomic_t count; /* reference count */ + struct user_struct *user; /* user who opened this dev */ + 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->sem 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_watch - represents a watch request on a specific inode + * + * d_list is protected by dev->sem of the associated watch->dev. + * i_list and mask are protected by inode->inotify_sem of the associated inode. + * dev, inode, and wd are never written to once the watch is created. + */ +struct inotify_watch { + struct list_head d_list; /* entry in inotify_device's list */ + struct list_head i_list; /* entry in inode's list */ + atomic_t count; /* reference count */ + struct inotify_device *dev; /* associated device */ + struct inode *inode; /* associated inode */ + s32 wd; /* watch descriptor */ + u32 mask; /* event mask for this watch */ +}; + +static inline void get_inotify_dev(struct inotify_device *dev) +{ + atomic_inc(&dev->count); +} + +static inline void put_inotify_dev(struct inotify_device *dev) +{ + if (atomic_dec_and_test(&dev->count)) { + atomic_dec(&dev->user->inotify_devs); + free_uid(dev->user); + kfree(dev); + } +} + +static inline void get_inotify_watch(struct inotify_watch *watch) +{ + atomic_inc(&watch->count); +} + +/* + * put_inotify_watch - decrements the ref count on a given watch. cleans up + * the watch and its references if the count reaches zero. + */ +static inline void put_inotify_watch(struct inotify_watch *watch) +{ + if (atomic_dec_and_test(&watch->count)) { + put_inotify_dev(watch->dev); + iput(watch->inode); + kmem_cache_free(watch_cachep, watch); + } +} + +/* + * kernel_event - create a new kernel event with the given parameters + * + * This function can sleep. + */ +static struct inotify_kernel_event * kernel_event(s32 wd, u32 mask, u32 cookie, + const char *name) +{ + struct inotify_kernel_event *kevent; + + kevent = kmem_cache_alloc(event_cachep, GFP_KERNEL); + if (unlikely(!kevent)) + 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; + } + + 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; + } + + return kevent; +} + +/* + * inotify_dev_get_event - return the next event in the given dev's queue + * + * Caller must hold dev->sem. + */ +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); +} + +/* + * inotify_dev_queue_event - add a new event to the given device + * + * Caller must hold dev->sem. Can sleep (calls kernel_event()). + */ +static void inotify_dev_queue_event(struct inotify_device *dev, + struct inotify_watch *watch, u32 mask, + u32 cookie, const char *name) +{ + struct inotify_kernel_event *kevent, *last; + + /* coalescing: drop this event if it is a dupe of the previous */ + last = inotify_dev_get_event(dev); + if (last && last->event.mask == mask && last->event.wd == watch->wd && + last->event.cookie == cookie) { + const char *lastname = last->name; + + if (!name && !lastname) + return; + if (name && lastname && !strcmp(lastname, name)) + return; + } + + /* the queue overflowed and we already sent the Q_OVERFLOW event */ + if (unlikely(dev->event_count > dev->max_events)) + return; + + /* 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(watch->wd, mask, cookie, name); + + if (unlikely(!kevent)) + return; + |