Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S261259AbVBJS4E (ORCPT ); Thu, 10 Feb 2005 13:56:04 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S261270AbVBJS4E (ORCPT ); Thu, 10 Feb 2005 13:56:04 -0500 Received: from peabody.ximian.com ([130.57.169.10]:55236 "EHLO peabody.ximian.com") by vger.kernel.org with ESMTP id S261259AbVBJSwp (ORCPT ); Thu, 10 Feb 2005 13:52:45 -0500 Subject: [patch] inotify for 2.6.11-rc3-mm2 From: Robert Love To: Andrew Morton Cc: linux-kernel@vger.kernel.org, John McCutchan In-Reply-To: <20050210023508.3583cf87.akpm@osdl.org> References: <20050210023508.3583cf87.akpm@osdl.org> Content-Type: text/plain Date: Thu, 10 Feb 2005 13:47:19 -0500 Message-Id: <1108061239.18124.18.camel@betsy.boston.ximian.com> Mime-Version: 1.0 X-Mailer: Evolution 2.0.3 Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 54660 Lines: 1975 On Thu, 2005-02-10 at 02:35 -0800, Andrew Morton wrote: > -inotify.patch > -inotify-fix_find_inode.patch > > I think my version is old, and it oopses. It is old. I have sent you multiple updates. ;-) Attached, find a patch against 2.6.11-rc3-mm2 of the latest inotify. This version has numerous optimizations, bug fixes, and clean ups. It introduces a generic notification layer to cleanly wrap both dnotify and inotify hooks in fs/. Pending is a data structure reorganization, to untangle some of the locking. Andrew, please apply! Robert Love inotify! inotify is intended to correct the deficiencies of dnotify, particularly its inability to scale and its terrible user interface: * dnotify requires the opening of one fd per each directory that you intend to watch. This quickly results in too many open files and pins removable media, preventing unmount. * dnotify is directory-based. You only learn about changes to directories. Sure, a change to a file in a directory affects the directory, but you are then forced to keep a cache of stat structures. * dnotify's interface to user-space is awful. Signals? inotify provides a more usable, simple, powerful solution to file change notification: * inotify's interface is a device node, not SIGIO. You open a single fd to the device node, which is select()-able. * inotify has an event that says "the filesystem that the item you were watching is on was unmounted." * inotify can watch directories or files. Inotify is currently used by Beagle (a desktop search infrastructure) and Gamin (a FAM replacement). Signed-off-by: Robert Love arch/sparc64/Kconfig | 13 drivers/char/Kconfig | 13 drivers/char/Makefile | 2 drivers/char/inotify.c | 1053 +++++++++++++++++++++++++++++++++++++++++++++ fs/attr.c | 33 - fs/compat.c | 14 fs/file_table.c | 4 fs/inode.c | 3 fs/namei.c | 38 - fs/open.c | 9 fs/read_write.c | 24 - fs/super.c | 3 include/linux/fs.h | 7 include/linux/fsnotify.h | 235 ++++++++++ include/linux/inotify.h | 118 +++++ include/linux/miscdevice.h | 1 include/linux/sched.h | 2 kernel/user.c | 2 18 files changed, 1511 insertions(+), 63 deletions(-) diff -urN linux-2.6.11-rc3-mm2/arch/sparc64/Kconfig linux-mm-inotify/arch/sparc64/Kconfig --- linux-2.6.11-rc3-mm2/arch/sparc64/Kconfig 2005-02-10 13:17:32.212175080 -0500 +++ linux-mm-inotify/arch/sparc64/Kconfig 2005-02-10 13:18:40.358815216 -0500 @@ -88,6 +88,19 @@ bool default y +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 SMP bool "Symmetric multi-processing support" ---help--- diff -urN linux-2.6.11-rc3-mm2/drivers/char/inotify.c linux-mm-inotify/drivers/char/inotify.c --- linux-2.6.11-rc3-mm2/drivers/char/inotify.c 1969-12-31 19:00:00.000000000 -0500 +++ linux-mm-inotify/drivers/char/inotify.c 2005-02-10 13:18:40.360814912 -0500 @@ -0,0 +1,1053 @@ +/* + * drivers/char/inotify.c - inode-based file event notifications + * + * Authors: + * John McCutchan + * Robert Love + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static atomic_t inotify_cookie; +static kmem_cache_t *watch_cachep; +static kmem_cache_t *event_cachep; +static kmem_cache_t *inode_data_cachep; + +static int sysfs_attrib_max_user_devices; +static int sysfs_attrib_max_user_watches; +static unsigned int sysfs_attrib_max_queued_events; + +/* + * struct inotify_device - represents an open instance of an inotify device + * + * For each inotify device, we need to keep track of the events queued on it, + * a list of the inodes that we are watching, and so on. + * + * This structure is protected by 'lock'. Lock ordering: + * + * dev->lock (protects dev) + * inode_lock (used to safely walk inode_in_use list) + * inode->i_lock (only needed for getting ref on inode_data) + */ +struct inotify_device { + wait_queue_head_t wait; + struct idr idr; + struct list_head events; + struct list_head watches; + spinlock_t lock; + unsigned int queue_size; + unsigned int event_count; + unsigned int max_events; + struct user_struct *user; +}; + +struct inotify_watch { + s32 wd; /* watch descriptor */ + u32 mask; /* event mask for this watch */ + struct inode *inode; /* associated inode */ + struct inotify_device *dev; /* associated device */ + struct list_head d_list; /* entry in device's list */ + struct list_head i_list; /* entry in inotify_data's list */ +}; + +/* + * A list of these is attached to each instance of the driver. In read(), this + * this list is walked and all events that can fit in the buffer are returned. + */ +struct inotify_kernel_event { + struct inotify_event event; + struct list_head list; + char *filename; +}; + +static ssize_t show_max_queued_events(struct class_device *class, char *buf) +{ + return sprintf(buf, "%d\n", sysfs_attrib_max_queued_events); +} + +static ssize_t store_max_queued_events(struct class_device *class, + const char *buf, size_t count) +{ + unsigned int max; + + if (sscanf(buf, "%u", &max) > 0 && max > 0) { + sysfs_attrib_max_queued_events = max; + return strlen(buf); + } + return -EINVAL; +} + +static ssize_t show_max_user_devices(struct class_device *class, char *buf) +{ + return sprintf(buf, "%d\n", sysfs_attrib_max_user_devices); +} + +static ssize_t store_max_user_devices(struct class_device *class, + const char *buf, size_t count) +{ + int max; + + if (sscanf(buf, "%d", &max) > 0 && max > 0) { + sysfs_attrib_max_user_devices = max; + return strlen(buf); + } + return -EINVAL; +} + +static ssize_t show_max_user_watches(struct class_device *class, char *buf) +{ + return sprintf(buf, "%d\n", sysfs_attrib_max_user_watches); +} + +static ssize_t store_max_user_watches(struct class_device *class, + const char *buf, size_t count) +{ + int max; + + if (sscanf(buf, "%d", &max) > 0 && max > 0) { + sysfs_attrib_max_user_watches = max; + return strlen(buf); + } + return -EINVAL; +} + +static CLASS_DEVICE_ATTR(max_queued_events, S_IRUGO | S_IWUSR, + show_max_queued_events, store_max_queued_events); +static CLASS_DEVICE_ATTR(max_user_devices, S_IRUGO | S_IWUSR, + show_max_user_devices, store_max_user_devices); +static CLASS_DEVICE_ATTR(max_user_watches, S_IRUGO | S_IWUSR, + show_max_user_watches, store_max_user_watches); + +static inline void __get_inode_data(struct inotify_inode_data *data) +{ + atomic_inc(&data->count); +} + +/* + * get_inode_data - pin an inotify_inode_data structure. Returns the structure + * if successful and NULL on failure, which can only occur if inotify_data is + * not yet allocated. The inode must be pinned prior to invocation. + */ +static inline struct inotify_inode_data * get_inode_data(struct inode *inode) +{ + struct inotify_inode_data *data; + + spin_lock(&inode->i_lock); + data = inode->inotify_data; + if (data) + __get_inode_data(data); + spin_unlock(&inode->i_lock); + + return data; +} + +/* + * put_inode_data - drop our reference on an inotify_inode_data and the + * inode structure in which it lives. If the reference count on inotify_data + * reaches zero, free it. + */ +static inline void put_inode_data(struct inode *inode) +{ + //spin_lock(&inode->i_lock); + if (atomic_dec_and_test(&inode->inotify_data->count)) { + kmem_cache_free(inode_data_cachep, inode->inotify_data); + inode->inotify_data = NULL; + } + //spin_unlock(&inode->i_lock); +} + +/* + * find_inode - resolve a user-given path to a specific inode and return a nd + */ +static int find_inode(const char __user *dirname, struct nameidata *nd) +{ + int error; + + error = __user_walk(dirname, LOOKUP_FOLLOW, nd); + if (error) + return error; + + /* you can only watch an inode if you have read permissions on it */ + return permission(nd->dentry->d_inode, MAY_READ, NULL); +} + +static struct inotify_kernel_event * kernel_event(s32 wd, u32 mask, u32 cookie, + const char *filename) +{ + struct inotify_kernel_event *kevent; + + kevent = kmem_cache_alloc(event_cachep, GFP_ATOMIC); + if (!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 (filename) { + 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(filename) + 1; + rem = event_size - len; + if (len > event_size) { + rem = event_size - (len % event_size); + if (len % event_size == 0) + rem = 0; + } + len += rem; + + kevent->filename = kmalloc(len, GFP_ATOMIC); + if (!kevent->filename) { + kmem_cache_free(event_cachep, kevent); + return NULL; + } + memset(kevent->filename, 0, len); + strncpy(kevent->filename, filename, strlen(filename)); + kevent->event.len = len; + } else { + kevent->event.len = 0; + kevent->filename = NULL; + } + + return kevent; +} + +#define list_to_inotify_kernel_event(pos) \ + list_entry((pos), struct inotify_kernel_event, list) + +#define inotify_dev_get_event(dev) \ + (list_to_inotify_kernel_event(dev->events.next)) + +/* + * inotify_dev_queue_event - add a new event to the given device + * + * Caller must hold dev->lock. + */ +static void inotify_dev_queue_event(struct inotify_device *dev, + struct inotify_watch *watch, u32 mask, + u32 cookie, const char *filename) +{ + struct inotify_kernel_event *kevent, *last; + + /* drop this event if it is a dupe of the previous */ + last = inotify_dev_get_event(dev); + if (dev->event_count && last->event.mask == mask && + last->event.wd == watch->wd) { + const char *lastname = last->filename; + + if (!filename && !lastname) + return; + if (filename && lastname && !strcmp(lastname, filename)) + return; + } + + /* + * the queue has already overflowed and we have already sent the + * Q_OVERFLOW event + */ + if (dev->event_count > dev->max_events) + return; + + /* the queue has just overflowed and we need to notify user space */ + if (dev->event_count == dev->max_events) { + kevent = kernel_event(-1, IN_Q_OVERFLOW, cookie, NULL); + goto add_event_to_queue; + } + + kevent = kernel_event(watch->wd, mask, cookie, filename); + +add_event_to_queue: + if (!kevent) + return; + + /* 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->wait); +} + +static inline int inotify_dev_has_events(struct inotify_device *dev) +{ + return !list_empty(&dev->events); +} + +/* + * inotify_dev_event_dequeue - destroy an event on the given device + * + * Caller must hold dev->lock. + */ +static void inotify_dev_event_dequeue(struct inotify_device *dev) +{ + struct inotify_kernel_event *kevent; + + if (!inotify_dev_has_events(dev)) + return; + + kevent = inotify_dev_get_event(dev); + list_del_init(&kevent->list); + if (kevent->filename) + kfree(kevent->filename); + + dev->event_count--; + dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len; + + kmem_cache_free(event_cachep, kevent); +} + +/* + * inotify_dev_get_wd - returns the next WD for use by the given dev + * + * This function can sleep. + */ +static int inotify_dev_get_wd(struct inotify_device *dev, + struct inotify_watch *watch) +{ + int ret; + + if (atomic_read(&dev->user->inotify_watches) >= + sysfs_attrib_max_user_watches) + return -ENOSPC; + +repeat: + if (!idr_pre_get(&dev->idr, GFP_KERNEL)) + return -ENOSPC; + spin_lock(&dev->lock); + ret = idr_get_new(&dev->idr, watch, &watch->wd); + spin_unlock(&dev->lock); + if (ret == -EAGAIN) /* more memory is required, try again */ + goto repeat; + else if (ret) /* the idr is full! */ + return -ENOSPC; + + atomic_inc(&dev->user->inotify_watches); + + return 0; +} + +/* + * inotify_dev_put_wd - release the given WD on the given device + * + * Caller must hold dev->lock. + */ +static int inotify_dev_put_wd(struct inotify_device *dev, s32 wd) +{ + if (!dev || wd < 0) + return -1; + + atomic_dec(&dev->user->inotify_watches); + idr_remove(&dev->idr, wd); + + return 0; +} + +/* + * create_watch - creates a watch on the given device. + * + * Grabs dev->lock, so the caller must not hold it. + */ +static struct inotify_watch *create_watch(struct inotify_device *dev, + u32 mask, struct inode *inode) +{ + struct inotify_watch *watch; + + watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL); + if (!watch) + return NULL; + + watch->mask = mask; + watch->inode = inode; + watch->dev = dev; + INIT_LIST_HEAD(&watch->d_list); + INIT_LIST_HEAD(&watch->i_list); + + if (inotify_dev_get_wd(dev, watch)) { + kmem_cache_free(watch_cachep, watch); + return NULL; + } + + return watch; +} + +/* + * delete_watch - removes the given 'watch' from the given 'dev' + * + * Caller must hold dev->lock. + */ +static void delete_watch(struct inotify_device *dev, + struct inotify_watch *watch) +{ + inotify_dev_put_wd(dev, watch->wd); + kmem_cache_free(watch_cachep, watch); +} + +/* + * inotify_find_dev - find the watch associated with the given inode and dev + * + * Caller must hold dev->lock. + * FIXME: Needs inotify_data->lock too. Don't need dev->lock, just pin it. + */ +static struct inotify_watch *inode_find_dev(struct inode *inode, + struct inotify_device *dev) +{ + struct inotify_watch *watch; + + if (!inode->inotify_data) + return NULL; + + list_for_each_entry(watch, &inode->inotify_data->watches, i_list) { + if (watch->dev == dev) + return watch; + } + + return NULL; +} + +/* + * dev_find_wd - given a (dev,wd) pair, returns the matching inotify_watcher + * + * Returns the results of looking up (dev,wd) in the idr layer. NULL is + * returned on error. + * + * The caller must hold dev->lock. + */ +static inline struct inotify_watch *dev_find_wd(struct inotify_device *dev, + u32 wd) +{ + return idr_find(&dev->idr, wd); +} + +static int inotify_dev_is_watching_inode(struct inotify_device *dev, + struct inode *inode) +{ + struct inotify_watch *watch; + + list_for_each_entry(watch, &dev->watches, d_list) { + if (watch->inode == inode) + return 1; + } + + return 0; +} + +/* + * inotify_dev_add_watcher - add the given watcher to the given device instance + * + * Caller must hold dev->lock. + */ +static int inotify_dev_add_watch(struct inotify_device *dev, + struct inotify_watch *watch) +{ + if (!dev || !watch) + return -EINVAL; + + list_add(&watch->d_list, &dev->watches); + return 0; +} + +/* + * inotify_dev_rm_watch - remove the given watch from the given device + * + * Caller must hold dev->lock because we call inotify_dev_queue_event(). + */ +static int inotify_dev_rm_watch(struct inotify_device *dev, + struct inotify_watch *watch) +{ + if (!watch) + return -EINVAL; + + inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL); + list_del_init(&watch->d_list); + + return 0; +} + +/* + * inode_add_watch - add a watch to the given inode + * + * Callers must hold dev->lock, because we call inode_find_dev(). + */ +static int inode_add_watch(struct inode *inode, struct inotify_watch *watch) +{ + int ret; + + if (!inode || !watch) + return -EINVAL; + + spin_lock(&inode->i_lock); + if (!inode->inotify_data) { + /* inotify_data is not attached to the inode, so add it */ + inode->inotify_data = kmem_cache_alloc(inode_data_cachep, + GFP_ATOMIC); + if (!inode->inotify_data) { + ret = -ENOMEM; + goto out_lock; + } + + atomic_set(&inode->inotify_data->count, 0); + INIT_LIST_HEAD(&inode->inotify_data->watches); + spin_lock_init(&inode->inotify_data->lock); + } else if (inode_find_dev(inode, watch->dev)) { + /* a watch is already associated with this (inode,dev) pair */ + ret = -EINVAL; + goto out_lock; + } + __get_inode_data(inode->inotify_data); + spin_unlock(&inode->i_lock); + + list_add(&watch->i_list, &inode->inotify_data->watches); + + return 0; +out_lock: + spin_unlock(&inode->i_lock); + return ret; +} + +static int inode_rm_watch(struct inode *inode, + struct inotify_watch *watch) +{ + if (!inode || !watch || !inode->inotify_data) + return -EINVAL; + + list_del_init(&watch->i_list); + + /* clean up inode->inotify_data */ + put_inode_data(inode); + + return 0; +} + +/* Kernel API */ + +/* + * inotify_inode_queue_event - queue an event with the given mask, cookie, and + * filename to any watches associated with the given inode. + * + * inode must be pinned prior to calling. + */ +void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie, + const char *name) +{ + struct inotify_watch *watch; + + if (!inode->inotify_data) + return; + + list_for_each_entry(watch, &inode->inotify_data->watches, i_list) { + if (watch->mask & mask) { + struct inotify_device *dev = watch->dev; + spin_lock(&dev->lock); + inotify_dev_queue_event(dev, watch, mask, cookie, name); + spin_unlock(&dev->lock); + } + } +} +EXPORT_SYMBOL_GPL(inotify_inode_queue_event); + +void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask, + u32 cookie, const char *filename) +{ + struct dentry *parent; + struct inode *inode; + + spin_lock(&dentry->d_lock); + parent = dentry->d_parent; + inode = parent->d_inode; + if (inode->inotify_data) { + dget(parent); + spin_unlock(&dentry->d_lock); + inotify_inode_queue_event(inode, mask, cookie, filename); + dput(parent); + } else + spin_unlock(&dentry->d_lock); +} +EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event); + +u32 inotify_get_cookie(void) +{ + atomic_inc(&inotify_cookie); + return atomic_read(&inotify_cookie); +} +EXPORT_SYMBOL_GPL(inotify_get_cookie); + +/* + * Caller must hold dev->lock. + */ +static void __remove_watch(struct inotify_watch *watch, + struct inotify_device *dev) +{ + struct inode *inode; + + inode = watch->inode; + + inode_rm_watch(inode, watch); + inotify_dev_rm_watch(dev, watch); + delete_watch(dev, watch); + + iput(inode); +} + +/* + * destroy_watch - remove a watch from both the device and the inode. + * + * watch->inode must be pinned. We drop a reference before returning. Grabs + * dev->lock. + */ +static void remove_watch(struct inotify_watch *watch) +{ + struct inotify_device *dev = watch->dev; + + spin_lock(&dev->lock); + __remove_watch(watch, dev); + spin_unlock(&dev->lock); +} + +void inotify_super_block_umount(struct super_block *sb) +{ + struct inode *inode; + + spin_lock(&inode_lock); + + /* + * We hold the inode_lock, so the inodes are not going anywhere, and + * we grab a reference on inotify_data before walking its list of + * watches. + */ + list_for_each_entry(inode, &inode_in_use, i_list) { + struct inotify_inode_data *inode_data; + struct inotify_watch *watch; + + if (inode->i_sb != sb) + continue; + + inode_data = get_inode_data(inode); + if (!inode_data) + continue; + + list_for_each_entry(watch, &inode_data->watches, i_list) { + struct inotify_device *dev = watch->dev; + spin_lock(&dev->lock); + inotify_dev_queue_event(dev, watch, IN_UNMOUNT, 0, + NULL); + __remove_watch(watch, dev); + spin_unlock(&dev->lock); + } + put_inode_data(inode); + } + + spin_unlock(&inode_lock); +} +EXPORT_SYMBOL_GPL(inotify_super_block_umount); + +/* + * inotify_inode_is_dead - an inode has been deleted, cleanup any watches + */ +void inotify_inode_is_dead(struct inode *inode) +{ + struct inotify_watch *watch, *next; + struct inotify_inode_data *data; + + data = get_inode_data(inode); + if (!data) + return; + list_for_each_entry_safe(watch, next, &data->watches, i_list) + remove_watch(watch); + put_inode_data(inode); +} +EXPORT_SYMBOL_GPL(inotify_inode_is_dead); + +/* The driver interface is implemented below */ + +static unsigned int inotify_poll(struct file *file, poll_table *wait) +{ + struct inotify_device *dev; + + dev = file->private_data; + + poll_wait(file, &dev->wait, wait); + + if (inotify_dev_has_events(dev)) + return POLLIN | POLLRDNORM; + + return 0; +} + +static ssize_t inotify_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + size_t event_size; + struct inotify_device *dev; + char __user *start; + DECLARE_WAITQUEUE(wait, current); + + start = buf; + dev = file->private_data; + + /* We only hand out full inotify events */ + event_size = sizeof(struct inotify_event); + if (count < event_size) + return 0; + + while (1) { + int has_events; + + spin_lock(&dev->lock); + has_events = inotify_dev_has_events(dev); + spin_unlock(&dev->lock); + if (has_events) + break; + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (signal_pending(current)) + return -EINTR; + + add_wait_queue(&dev->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + schedule(); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&dev->wait, &wait); + } + + while (count >= event_size) { + struct inotify_kernel_event *kevent; + + spin_lock(&dev->lock); + if (!inotify_dev_has_events(dev)) { + spin_unlock(&dev->lock); + break; + } + kevent = inotify_dev_get_event(dev); + spin_unlock(&dev->lock); + + /* We can't send this event, not enough space in the buffer */ + if (event_size + kevent->event.len > count) + break; + + /* Copy the entire event except the string to user space */ + if (copy_to_user(buf, &kevent->event, event_size)) + return -EFAULT; + + buf += event_size; + count -= event_size; + + /* Copy the filename to user space */ + if (kevent->filename) { + if (copy_to_user(buf, kevent->filename, + kevent->event.len)) + return -EFAULT; + buf += kevent->event.len; + count -= kevent->event.len; + } + + spin_lock(&dev->lock); + inotify_dev_event_dequeue(dev); + spin_unlock(&dev->lock); + } + + return buf - start; +} + +static int inotify_open(struct inode *inode, struct file *file) +{ + struct inotify_device *dev; + struct user_struct *user; + int ret; + + user = get_uid(current->user); + + if (atomic_read(&user->inotify_devs) >= sysfs_attrib_max_user_devices) { + ret = -EMFILE; + goto out_err; + } + + dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto out_err; + } + + atomic_inc(¤t->user->inotify_devs); + + idr_init(&dev->idr); + + INIT_LIST_HEAD(&dev->events); + INIT_LIST_HEAD(&dev->watches); + init_waitqueue_head(&dev->wait); + + dev->event_count = 0; + dev->queue_size = 0; + dev->max_events = sysfs_attrib_max_queued_events; + dev->user = user; + spin_lock_init(&dev->lock); + + file->private_data = dev; + + return 0; +out_err: + free_uid(current->user); + return ret; +} + +/* + * inotify_release_all_watches - destroy all watches on a given device + * + * FIXME: We need a lock on the watch here. + */ +static void inotify_release_all_watches(struct inotify_device *dev) +{ + struct inotify_watch *watch, *next; + + list_for_each_entry_safe(watch, next, &dev->watches, d_list) + remove_watch(watch); +} + +/* + * inotify_release_all_events - destroy all of the events on a given device + */ +static void inotify_release_all_events(struct inotify_device *dev) +{ + spin_lock(&dev->lock); + while (inotify_dev_has_events(dev)) + inotify_dev_event_dequeue(dev); + spin_unlock(&dev->lock); +} + +static int inotify_release(struct inode *inode, struct file *file) +{ + struct inotify_device *dev; + + dev = file->private_data; + + inotify_release_all_watches(dev); + inotify_release_all_events(dev); + + atomic_dec(&dev->user->inotify_devs); + free_uid(dev->user); + + kfree(dev); + + return 0; +} + +static int inotify_add_watch(struct inotify_device *dev, + struct inotify_watch_request *request) +{ + struct inode *inode; + struct inotify_watch *watch; + struct nameidata nd; + int ret; + + ret = find_inode((const char __user*) request->name, &nd); + if (ret) + return ret; + + /* held in place by references in nd */ + inode = nd.dentry->d_inode; + + spin_lock(&dev->lock); + + /* + * This handles the case of re-adding a directory we are already + * watching, we just update the mask and return 0 + */ + if (inotify_dev_is_watching_inode(dev, inode)) { + struct inotify_watch *owatch; /* the old watch */ + + owatch = inode_find_dev(inode, dev); + owatch->mask = request->mask; + spin_unlock(&dev->lock); + path_release(&nd); + + return owatch->wd; + } + + spin_unlock(&dev->lock); + + watch = create_watch(dev, request->mask, inode); + if (!watch) { + path_release(&nd); + return -ENOSPC; + } + + spin_lock(&dev->lock); + + /* We can't add anymore watches to this device */ + if (inotify_dev_add_watch(dev, watch)) { + delete_watch(dev, watch); + spin_unlock(&dev->lock); + path_release(&nd); + return -EINVAL; + } + + ret = inode_add_watch(inode, watch); + if (ret < 0) { + list_del_init(&watch->d_list); + delete_watch(dev, watch); + spin_unlock(&dev->lock); + path_release(&nd); + return ret; + } + + spin_unlock(&dev->lock); + + /* + * Demote the reference to nameidata to a reference to the inode held + * by the watch. + */ + spin_lock(&inode_lock); + __iget(inode); + spin_unlock(&inode_lock); + path_release(&nd); + + return watch->wd; +} + +static int inotify_ignore(struct inotify_device *dev, s32 wd) +{ + struct inotify_watch *watch; + int ret = 0; + + spin_lock(&dev->lock); + watch = dev_find_wd(dev, wd); + spin_unlock(&dev->lock); + if (!watch) { + ret = -EINVAL; + goto out; + } + __remove_watch(watch, dev); + +out: + spin_unlock(&dev->lock); + return ret; +} + +/* + * inotify_ioctl() - our device file's ioctl method + * + * The VFS serializes all of our calls via the BKL and we rely on that. We + * could, alternatively, grab dev->lock. Right now lower levels grab that + * where needed. + */ +static int inotify_ioctl(struct inode *ip, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + struct inotify_device *dev; + struct inotify_watch_request request; + void __user *p; + s32 wd; + + dev = fp->private_data; + p = (void __user *) arg; + + switch (cmd) { + case INOTIFY_WATCH: + if (copy_from_user(&request, p, sizeof (request))) + return -EFAULT; + return inotify_add_watch(dev, &request); + case INOTIFY_IGNORE: + if (copy_from_user(&wd, p, sizeof (wd))) + return -EFAULT; + return inotify_ignore(dev, wd); + case FIONREAD: + return put_user(dev->queue_size, (int __user *) p); + default: + return -ENOTTY; + } +} + +static struct file_operations inotify_fops = { + .owner = THIS_MODULE, + .poll = inotify_poll, + .read = inotify_read, + .open = inotify_open, + .release = inotify_release, + .ioctl = inotify_ioctl, +}; + +static struct miscdevice inotify_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "inotify", + .fops = &inotify_fops, +}; + +static int __init inotify_init(void) +{ + struct class_device *class; + int ret; + + ret = misc_register(&inotify_device); + if (ret) + return ret; + + sysfs_attrib_max_queued_events = 512; + sysfs_attrib_max_user_devices = 64; + sysfs_attrib_max_user_watches = 16384; + + class = inotify_device.class; + class_device_create_file(class, &class_device_attr_max_queued_events); + class_device_create_file(class, &class_device_attr_max_user_devices); + class_device_create_file(class, &class_device_attr_max_user_watches); + + atomic_set(&inotify_cookie, 0); + + watch_cachep = kmem_cache_create("inotify_watch_cache", + sizeof(struct inotify_watch), 0, SLAB_PANIC, + NULL, NULL); + + event_cachep = kmem_cache_create("inotify_event_cache", + sizeof(struct inotify_kernel_event), 0, + SLAB_PANIC, NULL, NULL); + + inode_data_cachep = kmem_cache_create("inotify_inode_data_cache", + sizeof(struct inotify_inode_data), 0, SLAB_PANIC, + NULL, NULL); + + printk(KERN_INFO "inotify device minor=%d\n", inotify_device.minor); + + return 0; +} + +module_init(inotify_init); diff -urN linux-2.6.11-rc3-mm2/drivers/char/Kconfig linux-mm-inotify/drivers/char/Kconfig --- linux-2.6.11-rc3-mm2/drivers/char/Kconfig 2005-02-10 13:17:46.349025952 -0500 +++ linux-mm-inotify/drivers/char/Kconfig 2005-02-10 13:18:40.361814760 -0500 @@ -62,6 +62,19 @@ depends on VT && !S390 && !USERMODE default y +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 SERIAL_NONSTANDARD bool "Non-standard serial port support" ---help--- diff -urN linux-2.6.11-rc3-mm2/drivers/char/Makefile linux-mm-inotify/drivers/char/Makefile --- linux-2.6.11-rc3-mm2/drivers/char/Makefile 2005-02-10 13:17:46.352025496 -0500 +++ linux-mm-inotify/drivers/char/Makefile 2005-02-10 13:18:40.362814608 -0500 @@ -9,6 +9,8 @@ obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o + +obj-$(CONFIG_INOTIFY) += inotify.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-y += misc.o diff -urN linux-2.6.11-rc3-mm2/fs/attr.c linux-mm-inotify/fs/attr.c --- linux-2.6.11-rc3-mm2/fs/attr.c 2005-02-10 13:17:35.850621952 -0500 +++ linux-mm-inotify/fs/attr.c 2005-02-10 13:19:39.655800704 -0500 @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -107,31 +107,8 @@ 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; @@ -194,11 +171,9 @@ 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 -urN linux-2.6.11-rc3-mm2/fs/compat.c linux-mm-inotify/fs/compat.c --- linux-2.6.11-rc3-mm2/fs/compat.c 2005-02-10 13:17:35.890615872 -0500 +++ linux-mm-inotify/fs/compat.c 2005-02-10 13:18:45.405048072 -0500 @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include #include @@ -1233,9 +1233,15 @@ 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, dentry->d_inode, + dentry->d_name.name); + else + fsnotify_modify(dentry, dentry->d_inode, + dentry->d_name.name); + } return ret; } diff -urN linux-2.6.11-rc3-mm2/fs/file_table.c linux-mm-inotify/fs/file_table.c --- linux-2.6.11-rc3-mm2/fs/file_table.c 2005-02-10 13:17:35.967604168 -0500 +++ linux-mm-inotify/fs/file_table.c 2005-02-10 13:18:45.406047920 -0500 @@ -16,6 +16,7 @@ #include #include #include +#include /* sysctl tunables... */ struct files_stat_struct files_stat = { @@ -122,6 +123,9 @@ struct vfsmount *mnt = file->f_vfsmnt; struct inode *inode = dentry->d_inode; + + fsnotify_close(dentry, inode, file->f_mode, dentry->d_name.name); + might_sleep(); /* * The function eventpoll_release() should be the first called diff -urN linux-2.6.11-rc3-mm2/fs/inode.c linux-mm-inotify/fs/inode.c --- linux-2.6.11-rc3-mm2/fs/inode.c 2005-02-10 13:17:47.899790200 -0500 +++ linux-mm-inotify/fs/inode.c 2005-02-10 13:18:45.407047768 -0500 @@ -132,6 +132,9 @@ #ifdef CONFIG_QUOTA memset(&inode->i_dquot, 0, sizeof(inode->i_dquot)); #endif +#ifdef CONFIG_INOTIFY + inode->inotify_data = NULL; +#endif inode->i_pipe = NULL; inode->i_bdev = NULL; inode->i_cdev = NULL; diff -urN linux-2.6.11-rc3-mm2/fs/namei.c linux-mm-inotify/fs/namei.c --- linux-2.6.11-rc3-mm2/fs/namei.c 2005-02-10 13:17:47.918787312 -0500 +++ linux-mm-inotify/fs/namei.c 2005-02-10 13:18:45.409047464 -0500 @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -1252,7 +1252,7 @@ DQUOT_INIT(dir); error = dir->i_op->create(dir, dentry, mode, nd); if (!error) { - inode_dir_notify(dir, DN_CREATE); + fsnotify_create(dir, dentry->d_name.name); security_inode_post_create(dir, dentry, mode); } return error; @@ -1557,7 +1557,7 @@ DQUOT_INIT(dir); error = dir->i_op->mknod(dir, dentry, mode, dev); if (!error) { - inode_dir_notify(dir, DN_CREATE); + fsnotify_create(dir, dentry->d_name.name); security_inode_post_mknod(dir, dentry, mode, dev); } return error; @@ -1630,7 +1630,7 @@ DQUOT_INIT(dir); error = dir->i_op->mkdir(dir, dentry, mode); if (!error) { - inode_dir_notify(dir, DN_CREATE); + fsnotify_mkdir(dir, dentry->d_name.name); security_inode_post_mkdir(dir,dentry, mode); } return error; @@ -1720,10 +1720,8 @@ } } up(&dentry->d_inode->i_sem); - if (!error) { - inode_dir_notify(dir, DN_DELETE); - d_delete(dentry); - } + if (!error) + fsnotify_rmdir(dentry, dentry->d_inode, dir); dput(dentry); return error; @@ -1793,10 +1791,9 @@ up(&dentry->d_inode->i_sem); /* We don't d_delete() NFS sillyrenamed files--they still exist. */ - if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) { - d_delete(dentry); - inode_dir_notify(dir, DN_DELETE); - } + if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) + fsnotify_unlink(dentry->d_inode, dir, dentry); + return error; } @@ -1870,7 +1867,7 @@ DQUOT_INIT(dir); error = dir->i_op->symlink(dir, dentry, oldname); if (!error) { - inode_dir_notify(dir, DN_CREATE); + fsnotify_create(dir, dentry->d_name.name); security_inode_post_symlink(dir, dentry, oldname); } return error; @@ -1943,7 +1940,7 @@ error = dir->i_op->link(old_dentry, dir, new_dentry); up(&old_dentry->d_inode->i_sem); if (!error) { - inode_dir_notify(dir, DN_CREATE); + fsnotify_create(dir, new_dentry->d_name.name); security_inode_post_link(old_dentry, dir, new_dentry); } return error; @@ -2107,6 +2104,7 @@ { int error; int is_dir = S_ISDIR(old_dentry->d_inode->i_mode); + char *old_name; if (old_dentry->d_inode == new_dentry->d_inode) return 0; @@ -2128,18 +2126,18 @@ DQUOT_INIT(old_dir); DQUOT_INIT(new_dir); + old_name = fsnotify_oldname_init(old_dentry); + if (is_dir) error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry); else error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry); if (!error) { - if (old_dir == new_dir) - inode_dir_notify(old_dir, DN_RENAME); - else { - inode_dir_notify(old_dir, DN_DELETE); - inode_dir_notify(new_dir, DN_CREATE); - } + const char *new_name = old_dentry->d_name.name; + fsnotify_move(old_dir, new_dir, old_name, new_name); } + fsnotify_oldname_free(old_name); + return error; } diff -urN linux-2.6.11-rc3-mm2/fs/open.c linux-mm-inotify/fs/open.c --- linux-2.6.11-rc3-mm2/fs/open.c 2005-02-10 13:17:36.101583800 -0500 +++ linux-mm-inotify/fs/open.c 2005-02-10 13:18:45.410047312 -0500 @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -944,9 +944,14 @@ fd = get_unused_fd(); if (fd >= 0) { struct file *f = filp_open(tmp, flags, mode); + struct dentry *dentry; + error = PTR_ERR(f); if (IS_ERR(f)) goto out_error; + dentry = f->f_dentry; + fsnotify_open(dentry, dentry->d_inode, + dentry->d_name.name); fd_install(fd, f); } out: @@ -998,7 +1003,7 @@ retval = err; } - dnotify_flush(filp, id); + fsnotify_flush(filp, id); locks_remove_posix(filp, id); fput(filp); return retval; diff -urN linux-2.6.11-rc3-mm2/fs/read_write.c linux-mm-inotify/fs/read_write.c --- linux-2.6.11-rc3-mm2/fs/read_write.c 2005-02-10 13:17:48.151751896 -0500 +++ linux-mm-inotify/fs/read_write.c 2005-02-10 13:31:10.191823272 -0500 @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -239,7 +239,10 @@ else ret = do_sync_read(file, buf, count, pos); if (ret > 0) { - dnotify_parent(file->f_dentry, DN_ACCESS); + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + fsnotify_access(dentry, inode, + dentry->d_name.name); current->rchar += ret; } current->syscr++; @@ -287,7 +290,10 @@ else ret = do_sync_write(file, buf, count, pos); if (ret > 0) { - dnotify_parent(file->f_dentry, DN_MODIFY); + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + fsnotify_modify(dentry, inode, + dentry->d_name.name); current->wchar += ret; } current->syscw++; @@ -523,9 +529,15 @@ 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; + struct inode *inode = dentry->d_inode; + + if (type == READ) + fsnotify_access(dentry, inode, dentry->d_name.name); + else + fsnotify_modify(dentry, inode, dentry->d_name.name); + } return ret; Efault: ret = -EFAULT; Files linux-2.6.11-rc3-mm2/fs/.read_write.c.swp and linux-mm-inotify/fs/.read_write.c.swp differ diff -urN linux-2.6.11-rc3-mm2/fs/super.c linux-mm-inotify/fs/super.c --- linux-2.6.11-rc3-mm2/fs/super.c 2005-02-10 13:17:36.202568448 -0500 +++ linux-mm-inotify/fs/super.c 2005-02-10 13:18:45.413046856 -0500 @@ -37,9 +37,9 @@ #include /* for the emergency remount stuff */ #include #include +#include #include - void get_filesystem(struct file_system_type *fs); void put_filesystem(struct file_system_type *fs); struct file_system_type *get_fs_type(const char *name); @@ -229,6 +229,7 @@ if (root) { sb->s_root = NULL; + fsnotify_sb_umount(sb); shrink_dcache_parent(root); shrink_dcache_anon(&sb->s_anon); dput(root); diff -urN linux-2.6.11-rc3-mm2/include/linux/fs.h linux-mm-inotify/include/linux/fs.h --- linux-2.6.11-rc3-mm2/include/linux/fs.h 2005-02-10 13:17:49.275581048 -0500 +++ linux-mm-inotify/include/linux/fs.h 2005-02-10 13:18:45.415046552 -0500 @@ -26,6 +26,7 @@ struct kstatfs; struct vm_area_struct; struct vfsmount; +struct inotify_inode_data; /* * It's silly to have NR_OPEN bigger than NR_FILE, but you can change @@ -474,6 +475,10 @@ struct dnotify_struct *i_dnotify; /* for directory notifications */ #endif +#ifdef CONFIG_INOTIFY + struct inotify_inode_data *inotify_data; +#endif + unsigned long i_state; unsigned long dirtied_when; /* jiffies of first dirtying */ @@ -1374,7 +1379,7 @@ extern int do_remount_sb(struct super_block *sb, int flags, void *data, int force); extern sector_t bmap(struct inode *, sector_t); -extern int setattr_mask(unsigned int); +extern void setattr_mask(unsigned int, int *, u32 *); extern int notify_change(struct dentry *, struct iattr *); extern int permission(struct inode *, int, struct nameidata *); extern int generic_permission(struct inode *, int, diff -urN linux-2.6.11-rc3-mm2/include/linux/fsnotify.h linux-mm-inotify/include/linux/fsnotify.h --- linux-2.6.11-rc3-mm2/include/linux/fsnotify.h 1969-12-31 19:00:00.000000000 -0500 +++ linux-mm-inotify/include/linux/fsnotify.h 2005-02-10 13:18:45.416046400 -0500 @@ -0,0 +1,235 @@ +#ifndef _LINUX_FS_NOTIFY_H +#define _LINUX_FS_NOTIFY_H + +/* + * include/linux/fs_notify.h - generic hooks for filesystem notification, to + * reduce in-source duplication from both dnotify and inotify. + * + * We don't compile any of this away in some complicated menagerie of ifdefs. + * Instead, we rely on the code inside to optimize away as needed. + * + * (C) Copyright 2005 Robert Love + */ + +#ifdef __KERNEL__ + +#include +#include + +/* + * fsnotify_move - file old_name at old_dir was moved to new_name at new_dir + */ +static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir, + const char *old_name, const char *new_name) +{ + u32 cookie; + + if (old_dir == new_dir) + inode_dir_notify(old_dir, DN_RENAME); + else { + inode_dir_notify(old_dir, DN_DELETE); + inode_dir_notify(new_dir, DN_CREATE); + } + + cookie = inotify_get_cookie(); + + inotify_inode_queue_event(old_dir, IN_MOVED_FROM, cookie, old_name); + inotify_inode_queue_event(new_dir, IN_MOVED_TO, cookie, new_name); +} + +/* + * fsnotify_unlink - file was unlinked + */ +static inline void fsnotify_unlink(struct inode *inode, struct inode *dir, + struct dentry *dentry) +{ + inode_dir_notify(dir, DN_DELETE); + inotify_inode_queue_event(dir, IN_DELETE_FILE, 0, dentry->d_name.name); + inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL); + + inotify_inode_is_dead(inode); + d_delete(dentry); +} + +/* + * fsnotify_rmdir - directory was removed + */ +static inline void fsnotify_rmdir(struct dentry *dentry, struct inode *inode, + struct inode *dir) +{ + inode_dir_notify(dir, DN_DELETE); + inotify_inode_queue_event(dir, IN_DELETE_SUBDIR,0,dentry->d_name.name); + inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL); + + inotify_inode_is_dead(inode); + d_delete(dentry); +} + +/* + * fsnotify_create - filename was linked in + */ +static inline void fsnotify_create(struct inode *inode, const char *filename) +{ + inode_dir_notify(inode, DN_CREATE); + inotify_inode_queue_event(inode, IN_CREATE_FILE, 0, filename); +} + +/* + * fsnotify_mkdir - directory 'name' was created + */ +static inline void fsnotify_mkdir(struct inode *inode, const char *name) +{ + inode_dir_notify(inode, DN_CREATE); + inotify_inode_queue_event(inode, IN_CREATE_SUBDIR, 0, name); +} + +/* + * fsnotify_access - file was read + */ +static inline void fsnotify_access(struct dentry *dentry, struct inode *inode, + const char *filename) +{ + dnotify_parent(dentry, DN_ACCESS); + inotify_dentry_parent_queue_event(dentry, IN_ACCESS, 0, + dentry->d_name.name); + inotify_inode_queue_event(inode, IN_ACCESS, 0, NULL); +} + +/* + * fsnotify_modify - file was modified + */ +static inline void fsnotify_modify(struct dentry *dentry, struct inode *inode, + const char *filename) +{ + dnotify_parent(dentry, DN_MODIFY); + inotify_dentry_parent_queue_event(dentry, IN_MODIFY, 0, filename); + inotify_inode_queue_event(inode, IN_MODIFY, 0, NULL); +} + +/* + * fsnotify_open - file was opened + */ +static inline void fsnotify_open(struct dentry *dentry, struct inode *inode, + const char *filename) +{ + inotify_inode_queue_event(inode, IN_OPEN, 0, NULL); + inotify_dentry_parent_queue_event(dentry, IN_OPEN, 0, filename); +} + +/* + * fsnotify_close - file was closed + */ +static inline void fsnotify_close(struct dentry *dentry, struct inode *inode, + mode_t mode, const char *filename) +{ + u32 mask; + + mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE; + inotify_dentry_parent_queue_event(dentry, mask, 0, filename); + inotify_inode_queue_event(inode, mask, 0, NULL); +} + +/* + * fsnotify_change - notify_change event. file was modified and/or metadata + * was changed. + */ +static inline void fsnotify_change(struct dentry *dentry, unsigned int ia_valid) +{ + int dn_mask = 0; + u32 in_mask = 0; + + if (ia_valid & ATTR_UID) { + in_mask |= IN_ATTRIB; + dn_mask |= DN_ATTRIB; + } + if (ia_valid & ATTR_GID) { + in_mask |= IN_ATTRIB; + dn_mask |= DN_ATTRIB; + } + if (ia_valid & ATTR_SIZE) { + in_mask |= IN_MODIFY; + dn_mask |= DN_MODIFY; + } + /* both times implies a utime(s) call */ + if ((ia_valid & (ATTR_ATIME | ATTR_MTIME)) == (ATTR_ATIME | ATTR_MTIME)) + { + in_mask |= IN_ATTRIB; + dn_mask |= DN_ATTRIB; + } else if (ia_valid & ATTR_ATIME) { + in_mask |= IN_ACCESS; + dn_mask |= DN_ACCESS; + } else if (ia_valid & ATTR_MTIME) { + in_mask |= IN_MODIFY; + dn_mask |= DN_MODIFY; + } + if (ia_valid & ATTR_MODE) { + in_mask |= IN_ATTRIB; + dn_mask |= DN_ATTRIB; + } + + if (dn_mask) + dnotify_parent(dentry, dn_mask); + if (in_mask) { + inotify_inode_queue_event(dentry->d_inode, in_mask, 0, NULL); + inotify_dentry_parent_queue_event(dentry, in_mask, 0, + dentry->d_name.name); + } +} + +/* + * fsnotify_sb_umount - filesystem unmount + */ +static inline void fsnotify_sb_umount(struct super_block *sb) +{ + inotify_super_block_umount(sb); +} + +/* + * fsnotify_flush - flush time! + */ +static inline void fsnotify_flush(struct file *filp, fl_owner_t id) +{ + dnotify_flush(filp, id); +} + +#ifdef CONFIG_INOTIFY /* inotify helpers */ + +/* + * fsnotify_oldname_init - save off the old filename before we change it + * + * this could be kstrdup if only we could add that to lib/string.c + */ +static inline char *fsnotify_oldname_init(struct dentry *old_dentry) +{ + char *old_name; + + old_name = kmalloc(strlen(old_dentry->d_name.name) + 1, GFP_KERNEL); + if (old_name) + strcpy(old_name, old_dentry->d_name.name); + return old_name; +} + +/* + * fsnotify_oldname_free - free the name we got from fsnotify_oldname_init + */ +static inline void fsnotify_oldname_free(const char *old_name) +{ + kfree(old_name); +} + +#else /* CONFIG_INOTIFY */ + +static inline char *fsnotify_oldname_init(struct dentry *old_dentry) +{ + return NULL; +} + +static inline void fsnotify_oldname_free(const char *old_name) +{ +} + +#endif /* ! CONFIG_INOTIFY */ + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_FS_NOTIFY_H */ diff -urN linux-2.6.11-rc3-mm2/include/linux/inotify.h linux-mm-inotify/include/linux/inotify.h --- linux-2.6.11-rc3-mm2/include/linux/inotify.h 1969-12-31 19:00:00.000000000 -0500 +++ linux-mm-inotify/include/linux/inotify.h 2005-02-10 13:18:45.417046248 -0500 @@ -0,0 +1,118 @@ +/* + * Inode based directory notification for Linux + * + * Copyright (C) 2005 John McCutchan + */ + +#ifndef _LINUX_INOTIFY_H +#define _LINUX_INOTIFY_H + +#include +#include + +/* + * struct inotify_event - structure read from the inotify device for each event + * + * When you are watching a directory, you will receive the filename for events + * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd. + */ +struct inotify_event { + __s32 wd; /* watch descriptor */ + __u32 mask; /* watch mask */ + __u32 cookie; /* cookie used for synchronizing two events */ + size_t len; /* length (including nulls) of name */ + char name[0]; /* stub for possible name */ +}; + +/* + * struct inotify_watch_request - represents a watch request + * + * Pass to the inotify device via the INOTIFY_WATCH ioctl + */ +struct inotify_watch_request { + char *name; /* directory name */ + __u32 mask; /* event mask */ +}; + +/* the following are legal, implemented events */ +#define IN_ACCESS 0x00000001 /* File was accessed */ +#define IN_MODIFY 0x00000002 /* File was modified */ +#define IN_ATTRIB 0x00000004 /* File changed attributes */ +#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ +#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define IN_OPEN 0x00000020 /* File was opened */ +#define IN_MOVED_FROM 0x00000040 /* File was moved from X */ +#define IN_MOVED_TO 0x00000080 /* File was moved to Y */ +#define IN_DELETE_SUBDIR 0x00000100 /* Subdir was deleted */ +#define IN_DELETE_FILE 0x00000200 /* Subfile was deleted */ +#define IN_CREATE_SUBDIR 0x00000400 /* Subdir was created */ +#define IN_CREATE_FILE 0x00000800 /* Subfile was created */ +#define IN_DELETE_SELF 0x00001000 /* Self was deleted */ +#define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ +#define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ +#define IN_IGNORED 0x00008000 /* File was ignored */ + +/* special flags */ +#define IN_ALL_EVENTS 0xffffffff /* All the events */ +#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) + +#define INOTIFY_IOCTL_MAGIC 'Q' +#define INOTIFY_IOCTL_MAXNR 2 + +#define INOTIFY_WATCH _IOR(INOTIFY_IOCTL_MAGIC, 1, struct inotify_watch_request) +#define INOTIFY_IGNORE _IOR(INOTIFY_IOCTL_MAGIC, 2, int) + +#ifdef __KERNEL__ + +#include +#include +#include + +struct inotify_inode_data { + struct list_head watches; /* list of watches on this inode */ + spinlock_t lock; /* lock protecting the struct */ + atomic_t count; /* ref count */ +}; + +#ifdef CONFIG_INOTIFY + +extern void inotify_inode_queue_event(struct inode *, __u32, __u32, + const char *); +extern void inotify_dentry_parent_queue_event(struct dentry *, __u32, __u32, + const char *); +extern void inotify_super_block_umount(struct super_block *); +extern void inotify_inode_is_dead(struct inode *); +extern __u32 inotify_get_cookie(void); + +#else + +static inline void inotify_inode_queue_event(struct inode *inode, + __u32 mask, __u32 cookie, + const char *filename) +{ +} + +static inline void inotify_dentry_parent_queue_event(struct dentry *dentry, + __u32 mask, __u32 cookie, + const char *filename) +{ +} + +static inline void inotify_super_block_umount(struct super_block *sb) +{ +} + +static inline void inotify_inode_is_dead(struct inode *inode) +{ +} + +static inline __u32 inotify_get_cookie(void) +{ + return 0; +} + +#endif /* CONFIG_INOTIFY */ + +#endif /* __KERNEL __ */ + +#endif /* _LINUX_INOTIFY_H */ diff -urN linux-2.6.11-rc3-mm2/include/linux/miscdevice.h linux-mm-inotify/include/linux/miscdevice.h --- linux-2.6.11-rc3-mm2/include/linux/miscdevice.h 2005-02-10 13:17:37.578359296 -0500 +++ linux-mm-inotify/include/linux/miscdevice.h 2005-02-10 13:18:45.418046096 -0500 @@ -2,6 +2,7 @@ #define _LINUX_MISCDEVICE_H #include #include +#include #define PSMOUSE_MINOR 1 #define MS_BUSMOUSE_MINOR 2 diff -urN linux-2.6.11-rc3-mm2/include/linux/sched.h linux-mm-inotify/include/linux/sched.h --- linux-2.6.11-rc3-mm2/include/linux/sched.h 2005-02-10 13:17:49.435556728 -0500 +++ linux-mm-inotify/include/linux/sched.h 2005-02-10 13:18:45.419045944 -0500 @@ -403,6 +403,8 @@ atomic_t processes; /* How many processes does this user have? */ atomic_t files; /* How many open files does this user have? */ atomic_t sigpending; /* How many pending signals does this user have? */ + atomic_t inotify_watches; /* How many inotify watches does this user have? */ + atomic_t inotify_devs; /* How many inotify devs does this user have opened? */ /* protected by mq_lock */ unsigned long mq_bytes; /* How many bytes can be allocated to mqueue? */ unsigned long locked_shm; /* How many pages of mlocked shm ? */ diff -urN linux-2.6.11-rc3-mm2/kernel/user.c linux-mm-inotify/kernel/user.c --- linux-2.6.11-rc3-mm2/kernel/user.c 2005-02-10 13:17:49.580534688 -0500 +++ linux-mm-inotify/kernel/user.c 2005-02-10 13:18:45.420045792 -0500 @@ -120,6 +120,8 @@ atomic_set(&new->processes, 0); atomic_set(&new->files, 0); atomic_set(&new->sigpending, 0); + atomic_set(&new->inotify_watches, 0); + atomic_set(&new->inotify_devs, 0); new->mq_bytes = 0; new->locked_shm = 0; - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/