linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [patch] inotify.
@ 2005-06-15 17:18 Robert Love
  2005-06-16 17:52 ` Zach Brown
  0 siblings, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-06-15 17:18 UTC (permalink / raw)
  To: linux-kernel; +Cc: Al Viro, John McCutchan, Andrew Morton

Below find an updated inotify, with a doubt the world's great inotify
release, against 2.6.12-rc6.

The only change from the last release is a bug fix in the unmount code,
fixed by Anton Altaparmakov, that was triggered by NTFS unmount.

Lot's of programs are using inotify and there is a lot of sample code
available.  A sampling:

Beagle, an Inotify-based Search Infrastructure:
http://beaglewiki.org/

Inotify support for Muine, a music player:
http://www.snorp.net/files/muine-inotify.tar.gz

Gamin, a FAM replacement, supports inotify:
http://www.gnome.org/~veillard/gamin/

Inotify Python bindings:
http://www.amautacorp.com/staff/Rudd-O/projects/pages/se-python-inotify

Test app and Glib bindings:
http://www.kernel.org/pub/linux/kernel/people/rml/

Best,

	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),
Gamin (a FAM replacement), and other projects.

See Documentation/filesystems/inotify.txt.

Signed-off-by: Robert Love <rml@novell.com>

 Documentation/filesystems/inotify.txt |  123 ++++
 fs/Kconfig                            |   13 
 fs/Makefile                           |    1 
 fs/attr.c                             |   33 -
 fs/compat.c                           |   12 
 fs/file_table.c                       |    3 
 fs/inode.c                            |    9 
 fs/inotify.c                          |  992 ++++++++++++++++++++++++++++++++++
 fs/namei.c                            |   30 -
 fs/nfsd/vfs.c                         |    6 
 fs/open.c                             |    4 
 fs/read_write.c                       |   15 
 fs/sysfs/file.c                       |    7 
 fs/xattr.c                            |    5 
 include/linux/fs.h                    |    7 
 include/linux/fsnotify.h              |  257 ++++++++
 include/linux/inotify.h               |  124 ++++
 include/linux/sched.h                 |    4 
 kernel/user.c                         |    4 
 19 files changed, 1585 insertions(+), 64 deletions(-)

diff -urN linux-2.6.12-rc6/Documentation/filesystems/inotify.txt linux/Documentation/filesystems/inotify.txt
--- linux-2.6.12-rc6/Documentation/filesystems/inotify.txt	1969-12-31 19:00:00.000000000 -0500
+++ linux/Documentation/filesystems/inotify.txt	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,123 @@
+				    inotify
+	     a powerful yet simple file change notification system
+
+
+
+Document started 15 Mar 2005 by Robert Love <rml@novell.com>
+
+(i) User Interface
+
+Inotify is controlled by a device node, /dev/inotify.  If you do not use udev,
+this device may need to be created manually.  First step, open it
+
+	int dev_fd = open ("/dev/inotify", O_RDONLY);
+
+Change events are managed by "watches".  A watch is an (object,mask) pair where
+the object is a file or directory and the mask is a bitmask of one or more
+inotify events that the application wishes to receive.  See <linux/inotify.h>
+for valid events.  A watch is referenced by a watch descriptor, or wd.
+
+Watches are added via a file descriptor.
+
+Watches on a directory will return events on any files inside of the directory.
+
+Adding a watch is simple,
+
+	/* 'wd' represents the watch on fd with mask */
+	struct inotify_request req = { fd, mask };
+	int wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+You can add a large number of files via something like
+
+	for each file to watch {
+		struct inotify_request req;
+		int file_fd;
+
+		file_fd = open (file, O_RDONLY);
+		if (fd < 0) {
+			perror ("open");
+			break;
+		}
+
+		req.fd = file_fd;
+		req.mask = mask;
+
+		wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+		close (fd);
+	}
+
+You can update an existing watch in the same manner, by passing in a new mask.
+
+An existing watch is removed via the INOTIFY_IGNORE ioctl, for example
+
+	ioctl (dev_fd, INOTIFY_IGNORE, wd);
+
+Events are provided in the form of an inotify_event structure that is read(2)
+from /dev/inotify.  The filename is of dynamic length and follows the struct.
+It is of size len.  The filename is padded with null bytes to ensure proper
+alignment.  This padding is reflected in len.
+
+You can slurp multiple events by passing a large buffer, for example
+
+	size_t len = read (fd, buf, BUF_LEN);
+
+Will return as many events as are available and fit in BUF_LEN.
+
+/dev/inotify is also select() and poll() able.
+
+You can find the size of the current event queue via the FIONREAD ioctl.
+
+All watches are destroyed and cleaned up on close.
+
+
+(ii) Internal Kernel Implementation
+
+Each open inotify device is associated with an inotify_device structure.
+
+Each watch is associated with an inotify_watch structure.  Watches are chained
+off of each associated device and each associated inode.
+
+See fs/inotify.c for the locking and lifetime rules.
+
+
+(iii) Rationale
+
+Q: What is the design decision behind not tying the watch to the
+open fd of the watched object?
+
+A: Watches are associated with an open inotify device, not an
+open file.  This solves the primary problem with dnotify:
+keeping the file open pins the file and thus, worse, pins the
+mount.  Dnotify is therefore infeasible for use on a desktop
+system with removable media as the media cannot be unmounted.
+
+Q: What is the design decision behind using an-fd-per-device as
+opposed to an fd-per-watch?
+
+A: An fd-per-watch quickly consumes more file descriptors than
+are allowed, more fd's than are feasible to manage, and more
+fd's than are ideally select()-able.  Yes, root can bump the
+per-process fd limit and yes, users can use epoll, but requiring
+both is silly and an extraneous requirement.  A watch consumes
+less memory than an open file, separating the number spaces is
+thus sensible.  The current design is what user-space developers
+want: Users open the device, once, and add n watches, requiring
+but one fd and no twiddling with fd limits.
+Opening /dev/inotify two thousand times is silly.  If we can
+implement user-space's preferences cleanly--and we can, the idr
+layer makes stuff like this trivial--then we should.
+
+Q: Why a device node?
+
+A: The second biggest problem with dnotify is that the user
+interface sucks ass.  Signals are a terrible, terrible interface
+for file notification.  Or for anything, for that matter.  The
+idea solution, from all perspectives, is a file descriptor based
+one that allows basic file I/O and poll/select.  Obtaining the
+fd and managing the watches could of been done either via a
+device file or a family of new system calls.  We decided to
+implement a device file because adding three or four new system
+calls that mirrored open, close, and ioctl seemed silly.  A
+character device makes sense from user-space and was easy to
+implement inside of the kernel.
diff -urN linux-2.6.12-rc6/fs/attr.c linux/fs/attr.c
--- linux-2.6.12-rc6/fs/attr.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/attr.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 @@
 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 @@
 	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.12-rc6/fs/compat.c linux/fs/compat.c
--- linux-2.6.12-rc6/fs/compat.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/compat.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 @@
 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 -urN linux-2.6.12-rc6/fs/file_table.c linux/fs/file_table.c
--- linux-2.6.12-rc6/fs/file_table.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/file_table.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 = {
@@ -123,6 +124,8 @@
 	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 -urN linux-2.6.12-rc6/fs/inode.c linux/fs/inode.c
--- linux-2.6.12-rc6/fs/inode.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/inode.c	2005-06-13 11:28:07.000000000 -0400
@@ -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:
@@ -128,6 +129,9 @@
 #ifdef CONFIG_QUOTA
 		memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));
 #endif
+#ifdef CONFIG_INOTIFY
+		atomic_set(&inode->inotify_watch_count, 0);
+#endif
 		inode->i_pipe = NULL;
 		inode->i_bdev = NULL;
 		inode->i_cdev = NULL;
@@ -202,6 +206,10 @@
 	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);
@@ -346,6 +354,7 @@
 
 	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);
 
diff -urN linux-2.6.12-rc6/fs/inotify.c linux/fs/inotify.c
--- linux-2.6.12-rc6/fs/inotify.c	1969-12-31 19:00:00.000000000 -0500
+++ linux/fs/inotify.c	2005-06-15 11:13:34.000000000 -0400
@@ -0,0 +1,992 @@
+/*
+ * 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/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 int max_user_devices;
+static int max_user_watches;
+static unsigned int 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 intofiy 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 ssize_t show_max_queued_events(struct class_device *class, char *buf)
+{
+	return sprintf(buf, "%d\n", 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) {
+		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", 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) {
+		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", 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) {
+		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_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;
+
+	/* 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);
+}
+
+/*
+ * remove_kevent - cleans up and ultimately frees the given kevent
+ *
+ * Caller must hold dev->sem.
+ */
+static void remove_kevent(struct inotify_device *dev,
+			  struct inotify_kernel_event *kevent)
+{
+	list_del(&kevent->list);
+
+	dev->event_count--;
+	dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len;
+
+	kfree(kevent->name);
+	kmem_cache_free(event_cachep, kevent);
+}
+
+/*
+ * inotify_dev_event_dequeue - destroy an event on the given device
+ *
+ * Caller must hold dev->sem.
+ */
+static void inotify_dev_event_dequeue(struct inotify_device *dev)
+{
+	if (!list_empty(&dev->events)) {
+		struct inotify_kernel_event *kevent;
+		kevent = inotify_dev_get_event(dev);
+		remove_kevent(dev, kevent);
+	}
+}
+
+/*
+ * inotify_dev_get_wd - returns the next WD for use by the given dev
+ *
+ * Callers must hold dev->sem.  This function can sleep.
+ */
+static int inotify_dev_get_wd(struct inotify_device *dev,
+			      struct inotify_watch *watch)
+{
+	int ret;
+
+	do {
+		if (unlikely(!idr_pre_get(&dev->idr, GFP_KERNEL)))
+			return -ENOSPC;
+		ret = idr_get_new(&dev->idr, watch, &watch->wd);
+	} while (ret == -EAGAIN);
+
+	return ret;
+}
+
+/*
+ * create_watch - creates a watch on the given device.
+ *
+ * Callers must hold dev->sem.  Calls inotify_dev_get_wd() so may sleep.
+ * Both 'dev' and 'inode' (by way of nameidata) need to be pinned.
+ */
+static struct inotify_watch *create_watch(struct inotify_device *dev,
+					  u32 mask, struct inode *inode)
+{
+	struct inotify_watch *watch;
+	int ret;
+
+	if (atomic_read(&dev->user->inotify_watches) >= max_user_watches)
+		return ERR_PTR(-ENOSPC);
+
+	watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL);
+	if (unlikely(!watch))
+		return ERR_PTR(-ENOMEM);
+
+	ret = inotify_dev_get_wd(dev, watch);
+	if (unlikely(ret)) {
+		kmem_cache_free(watch_cachep, watch);
+		return ERR_PTR(ret);
+	}
+
+	watch->mask = mask;
+	atomic_set(&watch->count, 0);
+	INIT_LIST_HEAD(&watch->d_list);
+	INIT_LIST_HEAD(&watch->i_list);
+
+	/* save a reference to device and bump the count to make it official */
+	get_inotify_dev(dev);
+	watch->dev = dev;
+
+	/*
+	 * Save a reference to the inode and bump the ref count to make it
+	 * official.  We hold a reference to nameidata, which makes this safe.
+	 */
+	watch->inode = igrab(inode);
+
+	/* bump our own count, corresponding to our entry in dev->watches */
+	get_inotify_watch(watch);
+
+	atomic_inc(&dev->user->inotify_watches);
+	atomic_inc(&watch->inode->inotify_watch_count);
+
+	return watch;
+}
+
+/*
+ * inotify_find_dev - find the watch associated with the given inode and dev
+ *
+ * Callers must hold inode->inotify_sem.
+ */
+static struct inotify_watch *inode_find_dev(struct inode *inode,
+					    struct inotify_device *dev)
+{
+	struct inotify_watch *watch;
+
+	list_for_each_entry(watch, &inode->inotify_watches, i_list) {
+		if (watch->dev == dev)
+			return watch;
+	}
+
+	return NULL;
+}
+
+/*
+ * inotify_dev_is_watching_inode - is this device watching this inode?
+ *
+ * Callers must hold dev->sem.
+ */
+static inline 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;
+}
+
+/*
+ * remove_watch_no_event - remove_watch() without the IN_IGNORED event.
+ */
+static void remove_watch_no_event(struct inotify_watch *watch,
+				  struct inotify_device *dev)
+{
+	list_del(&watch->i_list);
+	list_del(&watch->d_list);
+
+	atomic_dec(&watch->inode->inotify_watch_count);
+	atomic_dec(&dev->user->inotify_watches);
+	idr_remove(&dev->idr, watch->wd);
+	put_inotify_watch(watch);
+}
+
+/*
+ * remove_watch - Remove a watch from both the device and the inode.  Sends
+ * the IN_IGNORED event to the given device signifying that the inode is no
+ * longer watched.
+ *
+ * Callers must hold both inode->inotify_sem and dev->sem.  We drop a
+ * reference to the inode before returning.
+ *
+ * The inode is not iput() so as to remain atomic.  If the inode needs to be
+ * iput(), the call returns one.  Otherwise, it returns zero.
+ */
+static void remove_watch(struct inotify_watch *watch,struct inotify_device *dev)
+{
+	inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL);
+	remove_watch_no_event(watch, dev);
+}
+
+/* Kernel API */
+
+/**
+ * inotify_inode_queue_event - queue an event to all watches on this inode
+ * @inode: inode event is originating from
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
+			       const char *name)
+{
+	struct inotify_watch *watch, *next;
+
+	if (likely(!atomic_read(&inode->inotify_watch_count)))
+		return;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		u32 watch_mask = watch->mask;
+		if (watch_mask & mask) {
+			struct inotify_device *dev = watch->dev;
+			get_inotify_watch(watch);
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, mask, cookie, name);
+			if (watch_mask & IN_ONESHOT)
+				remove_watch_no_event(watch, dev);
+			up(&dev->sem);
+			put_inotify_watch(watch);
+		}
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_queue_event);
+
+/**
+ * inotify_dentry_parent_queue_event - queue an event to a dentry's parent
+ * @dentry: the dentry in question, we queue against this dentry's parent
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask,
+				       u32 cookie, const char *name)
+{
+	struct dentry *parent;
+	struct inode *inode;
+
+	spin_lock(&dentry->d_lock);
+	parent = dentry->d_parent;
+	inode = parent->d_inode;
+
+	if (unlikely(atomic_read(&inode->inotify_watch_count))) {
+		dget(parent);
+		spin_unlock(&dentry->d_lock);
+		inotify_inode_queue_event(inode, mask, cookie, name);
+		dput(parent);
+	} else
+		spin_unlock(&dentry->d_lock);
+}
+EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event);
+
+/**
+ * inotify_get_cookie - return a unique cookie for use in synchronizing events.
+ */
+u32 inotify_get_cookie(void)
+{
+	return atomic_inc_return(&inotify_cookie);
+}
+EXPORT_SYMBOL_GPL(inotify_get_cookie);
+
+/**
+ * inotify_unmount_inodes - an sb is unmounting.  handle any watched inodes.
+ * @list: list of inodes being unmounted (sb->s_inodes)
+ *
+ * Called with inode_lock held, protecting the unmounting super block's list
+ * of inodes, and with iprune_sem held, keeping shrink_icache_memory() at bay.
+ * We temporarily drop inode_lock, however, and CAN block.
+ */
+void inotify_unmount_inodes(struct list_head *list)
+{
+	struct inode *inode, *next_i;
+
+	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+		struct inotify_watch *watch, *next_w;
+		struct list_head *watches;
+
+		/*
+		 * We cannot __iget() an inode in state I_CLEAR, which is fine
+		 * as by that point the inode cannot have any watches.
+		 */
+		if (inode->i_state & I_CLEAR)
+			continue;
+
+		/* In case the remove_watch() drops a reference */
+		__iget(inode);
+
+		/*
+		 * We can safely drop inode_lock here because the per-sb list
+		 * of inodes must not change during unmount and iprune_sem
+		 * keeps shrink_icache_memory() away.
+		 */
+		spin_unlock(&inode_lock);
+
+		/* for each watch, send IN_UNMOUNT and then remove it */
+		down(&inode->inotify_sem);
+		watches = &inode->inotify_watches;
+		list_for_each_entry_safe(watch, next_w, watches, i_list) {
+			struct inotify_device *dev = watch->dev;
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, IN_UNMOUNT,0,NULL);
+			remove_watch(watch, dev);
+			up(&dev->sem);
+		}
+		up(&inode->inotify_sem);
+		iput(inode);		
+
+		spin_lock(&inode_lock);
+	}
+}
+EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
+
+/**
+ * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
+ * @inode: inode that is about to be removed
+ */
+void inotify_inode_is_dead(struct inode *inode)
+{
+	struct inotify_watch *watch, *next;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		struct inotify_device *dev = watch->dev;
+		down(&dev->sem);
+		remove_watch(watch, dev);
+		up(&dev->sem);
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_is_dead);
+
+/* Device Interface */
+
+static unsigned int inotify_poll(struct file *file, poll_table *wait)
+{
+	struct inotify_device *dev = file->private_data;
+	int ret = 0;
+
+	poll_wait(file, &dev->wq, wait);
+	down(&dev->sem);
+	if (!list_empty(&dev->events))
+		ret = POLLIN | POLLRDNORM;
+	up(&dev->sem);
+
+	return ret;
+}
+
+static ssize_t inotify_read(struct file *file, char __user *buf,
+			    size_t count, loff_t *pos)
+{
+	size_t event_size = sizeof (struct inotify_event);
+	struct inotify_device *dev;
+	char __user *start;
+	int ret;
+	DEFINE_WAIT(wait);
+
+	start = buf;
+	dev = file->private_data;
+
+	while (1) {
+		int events;
+
+		prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
+
+		down(&dev->sem);
+		events = !list_empty(&dev->events);
+		up(&dev->sem);
+		if (events) {
+			ret = 0;
+			break;
+		}
+
+		if (file->f_flags & O_NONBLOCK) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			ret = -EINTR;
+			break;
+		}
+
+		schedule();
+	}
+
+	finish_wait(&dev->wq, &wait);
+	if (ret)
+		return ret;
+
+	down(&dev->sem);
+	while (1) {
+		struct inotify_kernel_event *kevent;
+
+		ret = buf - start;
+		if (list_empty(&dev->events))
+			break;
+
+		kevent = inotify_dev_get_event(dev);
+		if (event_size + kevent->event.len > count)
+			break;
+
+		if (copy_to_user(buf, &kevent->event, event_size)) {
+			ret = -EFAULT;
+			break;
+		}
+		buf += event_size;
+		count -= event_size;
+
+		if (kevent->name) {
+			if (copy_to_user(buf, kevent->name, kevent->event.len)){
+				ret = -EFAULT;
+				break;
+			}
+			buf += kevent->event.len;
+			count -= kevent->event.len;
+		}
+
+		remove_kevent(dev, kevent);
+	}
+	up(&dev->sem);
+
+	return ret;
+}
+
+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 (unlikely(atomic_read(&user->inotify_devs) >= max_user_devices)) {
+		ret = -EMFILE;
+		goto out_err;
+	}
+
+	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	if (unlikely(!dev)) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	idr_init(&dev->idr);
+	INIT_LIST_HEAD(&dev->events);
+	INIT_LIST_HEAD(&dev->watches);
+	init_waitqueue_head(&dev->wq);
+	sema_init(&dev->sem, 1);
+	dev->event_count = 0;
+	dev->queue_size = 0;
+	dev->max_events = max_queued_events;
+	dev->user = user;
+	atomic_set(&dev->count, 0);
+
+	get_inotify_dev(dev);
+	atomic_inc(&user->inotify_devs);
+
+	file->private_data = dev;
+
+	nonseekable_open(inode, file);
+
+	return 0;
+out_err:
+	free_uid(user);
+	return ret;
+}
+
+static int inotify_release(struct inode *ignored, struct file *file)
+{
+	struct inotify_device *dev = file->private_data;
+
+	/*
+	 * Destroy all of the watches on this device.  Unfortunately, not very
+	 * pretty.  We cannot do a simple iteration over the list, because we
+	 * do not know the inode until we iterate to the watch.  But we need to
+	 * hold inode->inotify_sem before dev->sem.  The following works.
+	 */
+	while (1) {
+		struct inotify_watch *watch;
+		struct list_head *watches;
+		struct inode *inode;
+
+		down(&dev->sem);
+		watches = &dev->watches;
+		if (list_empty(watches)) {
+			up(&dev->sem);
+			break;
+		}
+		watch = list_entry(watches->next, struct inotify_watch, d_list);
+		get_inotify_watch(watch);
+		up(&dev->sem);
+
+		inode = watch->inode;
+		down(&inode->inotify_sem);
+		down(&dev->sem);
+		remove_watch_no_event(watch, dev);
+		up(&dev->sem);
+		up(&inode->inotify_sem);
+		put_inotify_watch(watch);
+	}
+
+	/* destroy all of the events on this device */
+	down(&dev->sem);
+	while (!list_empty(&dev->events))
+		inotify_dev_event_dequeue(dev);
+	up(&dev->sem);
+
+	/* free this device: the put matching the get in inotify_open() */
+	put_inotify_dev(dev);
+
+	return 0;
+}
+
+static int inotify_add_watch(struct inotify_device *dev, int fd, u32 mask)
+{
+	struct inotify_watch *watch, *old;
+	struct inode *inode;
+	struct file *filp;
+	int ret;
+
+	filp = fget(fd);
+	if (!filp)
+		return -EBADF;
+	inode = filp->f_dentry->d_inode;
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* don't let user-space set invalid bits: we don't want flags set */
+	mask &= IN_ALL_EVENTS;
+	if (!mask) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Handle the case of re-adding a watch on an (inode,dev) pair that we
+	 * are already watching.  We just update the mask and return its wd.
+	 */
+	old = inode_find_dev(inode, dev);
+	if (unlikely(old)) {
+		old->mask = mask;
+		ret = old->wd;
+		goto out;
+	}
+
+	watch = create_watch(dev, mask, inode);
+	if (unlikely(IS_ERR(watch))) {
+		ret = PTR_ERR(watch);
+		goto out;
+	}
+
+	/* Add the watch to the device's and the inode's list */
+	list_add(&watch->d_list, &dev->watches);
+	list_add(&watch->i_list, &inode->inotify_watches);
+	ret = watch->wd;
+
+out:
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	fput(filp);
+
+	return ret;
+}
+
+/*
+ * inotify_ignore - handle the INOTIFY_IGNORE ioctl, asking that a given wd be
+ * removed from the device.
+ *
+ * Can sleep.
+ */
+static int inotify_ignore(struct inotify_device *dev, s32 wd)
+{
+	struct inotify_watch *watch;
+	struct inode *inode;
+
+	down(&dev->sem);
+	watch = idr_find(&dev->idr, wd);
+	if (unlikely(!watch)) {
+		up(&dev->sem);
+		return -EINVAL;
+	}
+	get_inotify_watch(watch);
+	inode = watch->inode;
+	up(&dev->sem);
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* make sure that we did not race */
+	watch = idr_find(&dev->idr, wd);
+	if (likely(watch))
+		remove_watch(watch, dev);
+
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	put_inotify_watch(watch);
+
+	return 0;
+}
+
+static long inotify_ioctl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	struct inotify_device *dev;
+	struct inotify_watch_request request;
+	void __user *p;
+	int ret = -ENOTTY;
+	s32 wd;
+
+	dev = file->private_data;
+	p = (void __user *) arg;
+
+	switch (cmd) {
+	case INOTIFY_WATCH:
+		if (unlikely(copy_from_user(&request, p, sizeof (request)))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_add_watch(dev, request.fd, request.mask);
+		break;
+	case INOTIFY_IGNORE:
+		if (unlikely(get_user(wd, (int __user *) p))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_ignore(dev, wd);
+		break;
+	case FIONREAD:
+		ret = put_user(dev->queue_size, (int __user *) p);
+		break;
+	}
+
+	return ret;
+}
+
+static struct file_operations inotify_fops = {
+	.owner		= THIS_MODULE,
+	.poll		= inotify_poll,
+	.read		= inotify_read,
+	.open		= inotify_open,
+	.release	= inotify_release,
+	.unlocked_ioctl	= inotify_ioctl,
+	.compat_ioctl	= inotify_ioctl,
+};
+
+static struct miscdevice inotify_device = {
+	.minor  = MISC_DYNAMIC_MINOR,
+	.name	= "inotify",
+	.fops	= &inotify_fops,
+};
+
+/*
+ * inotify_init - Our initialization function.  Note that we cannnot return
+ * error because we have compiled-in VFS hooks.  So an (unlikely) failure here
+ * must result in panic().
+ */
+static int __init inotify_init(void)
+{
+	struct class_device *class;
+	int ret;
+
+	ret = misc_register(&inotify_device);
+	if (unlikely(ret))
+		panic("inotify: misc_register returned %d\n", ret);
+
+	max_queued_events = 8192;
+	max_user_devices = 128;
+	max_user_watches = 8192;
+
+	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);
+
+	printk(KERN_INFO "inotify device minor=%d\n", inotify_device.minor);
+
+	return 0;
+}
+
+module_init(inotify_init);
diff -urN linux-2.6.12-rc6/fs/Kconfig linux/fs/Kconfig
--- linux-2.6.12-rc6/fs/Kconfig	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/Kconfig	2005-06-13 11:28:04.000000000 -0400
@@ -339,6 +339,19 @@
 	  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 -urN linux-2.6.12-rc6/fs/Makefile linux/fs/Makefile
--- linux-2.6.12-rc6/fs/Makefile	2005-03-02 02:38:10.000000000 -0500
+++ linux/fs/Makefile	2005-06-13 11:28:04.000000000 -0400
@@ -11,6 +11,7 @@
 		attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \
 
+obj-$(CONFIG_INOTIFY)		+= inotify.o
 obj-$(CONFIG_EPOLL)		+= eventpoll.o
 obj-$(CONFIG_COMPAT)		+= compat.o
 
diff -urN linux-2.6.12-rc6/fs/namei.c linux/fs/namei.c
--- linux-2.6.12-rc6/fs/namei.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/namei.c	2005-06-13 11:28:04.000000000 -0400
@@ -21,7 +21,7 @@
 #include <linux/namei.h>
 #include <linux/quotaops.h>
 #include <linux/pagemap.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
 #include <linux/security.h>
@@ -1296,7 +1296,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;
@@ -1602,7 +1602,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;
@@ -1675,7 +1675,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;
@@ -1766,7 +1766,7 @@
 	}
 	up(&dentry->d_inode->i_sem);
 	if (!error) {
-		inode_dir_notify(dir, DN_DELETE);
+		fsnotify_rmdir(dentry, dentry->d_inode, dir);
 		d_delete(dentry);
 	}
 	dput(dentry);
@@ -1839,9 +1839,10 @@
 
 	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
 	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
+		fsnotify_unlink(dentry, dir);
 		d_delete(dentry);
-		inode_dir_notify(dir, DN_DELETE);
 	}
+
 	return error;
 }
 
@@ -1915,7 +1916,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;
@@ -1988,7 +1989,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;
@@ -2152,6 +2153,7 @@
 {
 	int error;
 	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	const char *old_name;
 
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
@@ -2173,18 +2175,18 @@
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
 
+	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
+
 	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, is_dir);
 	}
+	fsnotify_oldname_free(old_name);
+
 	return error;
 }
 
diff -urN linux-2.6.12-rc6/fs/nfsd/vfs.c linux/fs/nfsd/vfs.c
--- linux-2.6.12-rc6/fs/nfsd/vfs.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/nfsd/vfs.c	2005-06-13 11:28:04.000000000 -0400
@@ -45,7 +45,7 @@
 #endif /* CONFIG_NFSD_V3 */
 #include <linux/nfsd/nfsfh.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #ifdef CONFIG_NFSD_V4
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
@@ -861,7 +861,7 @@
 		nfsdstats.io_read += err;
 		*count = err;
 		err = 0;
-		dnotify_parent(file->f_dentry, DN_ACCESS);
+		fsnotify_access(file->f_dentry);
 	} else 
 		err = nfserrno(err);
 out:
@@ -917,7 +917,7 @@
 	set_fs(oldfs);
 	if (err >= 0) {
 		nfsdstats.io_write += cnt;
-		dnotify_parent(file->f_dentry, DN_MODIFY);
+		fsnotify_modify(file->f_dentry);
 	}
 
 	/* clear setuid/setgid flag after write */
diff -urN linux-2.6.12-rc6/fs/open.c linux/fs/open.c
--- linux-2.6.12-rc6/fs/open.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/open.c	2005-06-13 11:28:04.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/smp_lock.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
@@ -944,9 +944,11 @@
 		fd = get_unused_fd();
 		if (fd >= 0) {
 			struct file *f = filp_open(tmp, flags, mode);
+
 			error = PTR_ERR(f);
 			if (IS_ERR(f))
 				goto out_error;
+			fsnotify_open(f->f_dentry);
 			fd_install(fd, f);
 		}
 out:
diff -urN linux-2.6.12-rc6/fs/read_write.c linux/fs/read_write.c
--- linux-2.6.12-rc6/fs/read_write.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/read_write.c	2005-06-13 11:28:04.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/security.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
@@ -239,7 +239,7 @@
 			else
 				ret = do_sync_read(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_ACCESS);
+				fsnotify_access(file->f_dentry);
 				current->rchar += ret;
 			}
 			current->syscr++;
@@ -287,7 +287,7 @@
 			else
 				ret = do_sync_write(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_MODIFY);
+				fsnotify_modify(file->f_dentry);
 				current->wchar += ret;
 			}
 			current->syscw++;
@@ -523,9 +523,12 @@
 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) {
+		if (type == READ)
+			fsnotify_access(file->f_dentry);
+		else
+			fsnotify_modify(file->f_dentry);
+	}
 	return ret;
 Efault:
 	ret = -EFAULT;
diff -urN linux-2.6.12-rc6/fs/sysfs/file.c linux/fs/sysfs/file.c
--- linux-2.6.12-rc6/fs/sysfs/file.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/sysfs/file.c	2005-06-13 11:28:04.000000000 -0400
@@ -3,7 +3,7 @@
  */
 
 #include <linux/module.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/kobject.h>
 #include <asm/uaccess.h>
 #include <asm/semaphore.h>
@@ -389,9 +389,6 @@
  * sysfs_update_file - update the modified timestamp on an object attribute.
  * @kobj: object we're acting for.
  * @attr: attribute descriptor.
- *
- * Also call dnotify for the dentry, which lots of userspace programs
- * use.
  */
 int sysfs_update_file(struct kobject * kobj, const struct attribute * attr)
 {
@@ -406,7 +403,7 @@
 		if (victim->d_inode && 
 		    (victim->d_parent->d_inode == dir->d_inode)) {
 			victim->d_inode->i_mtime = CURRENT_TIME;
-			dnotify_parent(victim, DN_MODIFY);
+			fsnotify_modify(victim);
 
 			/**
 			 * Drop reference from initial sysfs_get_dentry().
diff -urN linux-2.6.12-rc6/fs/xattr.c linux/fs/xattr.c
--- linux-2.6.12-rc6/fs/xattr.c	2005-03-02 02:38:07.000000000 -0500
+++ linux/fs/xattr.c	2005-06-13 11:28:04.000000000 -0400
@@ -16,6 +16,7 @@
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/module.h>
+#include <linux/fsnotify.h>
 #include <asm/uaccess.h>
 
 /*
@@ -57,8 +58,10 @@
 		if (error)
 			goto out;
 		error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
-		if (!error)
+		if (!error) {
+			fsnotify_xattr(d);
 			security_inode_post_setxattr(d, kname, kvalue, size, flags);
+		}
 out:
 		up(&d->d_inode->i_sem);
 	}
diff -urN linux-2.6.12-rc6/include/linux/fs.h linux/include/linux/fs.h
--- linux-2.6.12-rc6/include/linux/fs.h	2005-06-07 15:47:53.000000000 -0400
+++ linux/include/linux/fs.h	2005-06-13 11:28:07.000000000 -0400
@@ -471,6 +471,12 @@
 	struct dnotify_struct	*i_dnotify; /* for directory notifications */
 #endif
 
+#ifdef CONFIG_INOTIFY
+	atomic_t		inotify_watch_count; /* number of watches */
+	struct list_head	inotify_watches; /* watches on this inode */
+	struct semaphore	inotify_sem;	/* protects the watches list */
+#endif
+
 	unsigned long		i_state;
 	unsigned long		dirtied_when;	/* jiffies of first dirtying */
 
@@ -1369,7 +1375,6 @@
 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 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.12-rc6/include/linux/fsnotify.h linux/include/linux/fsnotify.h
--- linux-2.6.12-rc6/include/linux/fsnotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/fsnotify.h	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,257 @@
+#ifndef _LINUX_FS_NOTIFY_H
+#define _LINUX_FS_NOTIFY_H
+
+/*
+ * include/linux/fsnotify.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 <linux/dnotify.h>
+#include <linux/inotify.h>
+
+/*
+ * 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,
+				 int isdir)
+{
+	u32 cookie = inotify_get_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);
+	}
+
+	if (isdir)
+		isdir = IN_ISDIR;
+	inotify_inode_queue_event(old_dir, IN_MOVED_FROM|isdir,cookie,old_name);
+	inotify_inode_queue_event(new_dir, IN_MOVED_TO|isdir, cookie, new_name);
+}
+
+/*
+ * fsnotify_unlink - file was unlinked
+ */
+static inline void fsnotify_unlink(struct dentry *dentry, struct inode *dir)
+{
+	struct inode *inode = dentry->d_inode;
+
+	inode_dir_notify(dir, DN_DELETE);
+	inotify_inode_queue_event(dir, IN_DELETE, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL);
+
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * 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|IN_ISDIR,0,dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF | IN_ISDIR, 0, NULL);
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * fsnotify_create - 'name' was linked in
+ */
+static inline void fsnotify_create(struct inode *inode, const char *name)
+{
+	inode_dir_notify(inode, DN_CREATE);
+	inotify_inode_queue_event(inode, IN_CREATE, 0, name);
+}
+
+/*
+ * 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 | IN_ISDIR, 0, name);
+}
+
+/*
+ * fsnotify_access - file was read
+ */
+static inline void fsnotify_access(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ACCESS;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_ACCESS);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_modify - file was modified
+ */
+static inline void fsnotify_modify(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_MODIFY;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_MODIFY);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_open - file was opened
+ */
+static inline void fsnotify_open(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_OPEN;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+}
+
+/*
+ * fsnotify_close - file was closed
+ */
+static inline void fsnotify_close(struct file *file)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+	mode_t mode = file->f_mode;
+	u32 mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_xattr - extended attributes were changed
+ */
+static inline void fsnotify_xattr(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ATTRIB;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	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)
+{
+	struct inode *inode = dentry->d_inode;
+	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) {
+		if (S_ISDIR(inode->i_mode))
+			in_mask |= IN_ISDIR;
+		inotify_inode_queue_event(inode, in_mask, 0, NULL);
+		inotify_dentry_parent_queue_event(dentry, in_mask, 0,
+						  dentry->d_name.name);
+	}
+}
+
+#ifdef CONFIG_INOTIFY	/* inotify helpers */
+
+/*
+ * fsnotify_oldname_init - save off the old filename before we change it
+ *
+ * XXX: This could be kstrdup if only we could add that to lib/string.c
+ */
+static inline const char *fsnotify_oldname_init(const char *name)
+{
+	size_t len;
+	char *buf;
+
+	len = strlen(name) + 1;
+	buf = kmalloc(len, GFP_KERNEL);
+	if (likely(buf))
+		memcpy(buf, name, len);
+	return buf;
+}
+
+/*
+ * 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 const char *fsnotify_oldname_init(const char *name)
+{
+	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.12-rc6/include/linux/inotify.h linux/include/linux/inotify.h
--- linux-2.6.12-rc6/include/linux/inotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/inotify.h	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,124 @@
+/*
+ * Inode based directory notification for Linux
+ *
+ * Copyright (C) 2005 John McCutchan
+ */
+
+#ifndef _LINUX_INOTIFY_H
+#define _LINUX_INOTIFY_H
+
+#include <linux/types.h>
+
+/*
+ * 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 to synchronize two events */
+	__u32		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 {
+	int		fd;		/* fd of filename to watch */
+	__u32		mask;		/* event mask */
+};
+
+/* the following are legal, implemented events that user-space can watch for */
+#define IN_ACCESS		0x00000001	/* File was accessed */
+#define IN_MODIFY		0x00000002	/* File was modified */
+#define IN_ATTRIB		0x00000004	/* Metadata changed */
+#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_CREATE		0x00000100	/* Subfile was created */
+#define IN_DELETE		0x00000200	/* Subfile was deleted */
+#define IN_DELETE_SELF		0x00000400	/* Self was deleted */
+
+/* the following are legal events.  they are sent as needed to any watch */
+#define IN_UNMOUNT		0x00002000	/* Backing fs was unmounted */
+#define IN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
+#define IN_IGNORED		0x00008000	/* File was ignored */
+
+/* helper events */
+#define IN_CLOSE		(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */
+#define IN_MOVE			(IN_MOVED_FROM | IN_MOVED_TO) /* moves */
+
+/* special flags */
+#define IN_ISDIR		0x40000000	/* event occurred against dir */
+#define IN_ONESHOT		0x80000000	/* only send event once */
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define IN_ALL_EVENTS	(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
+			 IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \
+			 IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF)
+
+#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 <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/config.h>
+
+#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_unmount_inodes(struct list_head *);
+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_unmount_inodes(struct list_head *list)
+{
+}
+
+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.12-rc6/include/linux/sched.h linux/include/linux/sched.h
--- linux-2.6.12-rc6/include/linux/sched.h	2005-06-07 15:47:53.000000000 -0400
+++ linux/include/linux/sched.h	2005-06-13 11:28:04.000000000 -0400
@@ -404,6 +404,10 @@
 	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? */
+#ifdef CONFIG_INOTIFY
+	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? */
+#endif
 	/* 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.12-rc6/kernel/user.c linux/kernel/user.c
--- linux-2.6.12-rc6/kernel/user.c	2005-06-07 15:47:53.000000000 -0400
+++ linux/kernel/user.c	2005-06-13 11:28:04.000000000 -0400
@@ -120,6 +120,10 @@
 		atomic_set(&new->processes, 0);
 		atomic_set(&new->files, 0);
 		atomic_set(&new->sigpending, 0);
+#ifdef CONFIG_INOTIFY
+		atomic_set(&new->inotify_watches, 0);
+		atomic_set(&new->inotify_devs, 0);
+#endif
 
 		new->mq_bytes = 0;
 		new->locked_shm = 0;



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-15 17:18 [patch] inotify Robert Love
@ 2005-06-16 17:52 ` Zach Brown
  2005-06-16 18:25   ` Robert Love
  0 siblings, 1 reply; 45+ messages in thread
From: Zach Brown @ 2005-06-16 17:52 UTC (permalink / raw)
  To: Robert Love; +Cc: linux-kernel, Al Viro, John McCutchan, Andrew Morton

Robert Love wrote:
> Below find an updated inotify, with a doubt the world's great inotify
> release, against 2.6.12-rc6.

some quick comments, nothing earth shattering..

> @@ -0,0 +1,123 @@

diff -p, please.

> + * struct inotify_kernel_event - An intofiy event, originating from a watch and

intofiy!

> +/*
> + * inotify_dev_is_watching_inode - is this device watching this inode?
> + *
> + * Callers must hold dev->sem.
> + */
> +static inline 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;
> +}

Am I missing where this is used?

> +       if (likely(!atomic_read(&inode->inotify_watch_count)))
> +               return;

Are there still platforms that implement atomic_read() with locks?  I
wonder if there isn't a cheaper way for inodes to find out that they're
not involved in inotify.. maybe an inode function pointer that is only
set to queue_event when watchers are around?

> +	down(&inode->inotify_sem);
> +	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
> +		u32 watch_mask = watch->mask;
> +		if (watch_mask & mask) {
> +			struct inotify_device *dev = watch->dev;
> +			get_inotify_watch(watch);
> +			down(&dev->sem);
> +			inotify_dev_queue_event(dev, watch, mask, cookie, name);

I wonder if its worth walking the watches as they're added and removed
to calculate a total mask of what is being watched so that uninteresting
events can be immediately ignored without walking the watches.

I also wonder about adding references to an event from each device that
is watching the inode rather than queueing events on all the devices.
If it only ever copies the full event to user space anyway, you'd just
need a series of pointers on the dev that hold references to a
refcounted event.  It looks like events are only ever dequeued from the
device as they're walked from the head of the list so it doesn't seem
like the ability to remove an event from the middle would be missed.

Too aggressive for a situation that rarely happens anyway?

> +	while (1) {
> +		int events;
> +
> +		prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
> +
> +		down(&dev->sem);
> +		events = !list_empty(&dev->events);
> +		up(&dev->sem);
> +		if (events) {
> +			ret = 0;
> +			break;
> +		}
> +
> +		if (file->f_flags & O_NONBLOCK) {
> +			ret = -EAGAIN;
> +			break;
> +		}
> +
> +		if (signal_pending(current)) {
> +			ret = -EINTR;
> +			break;
> +		}
> +
> +		schedule();
> +	}
> +
> +	finish_wait(&dev->wq, &wait);

It'd be nice to get some wait_event_interruptible() love to replace the
hand-rolled _wait()s and schedule().  It could probably be folded into
the second loop, I imagine.  I suspect that the signal_pending() case
wants to return ERESTARTSYS like w_e_i() does.

> +	nonseekable_open(inode, file);

While it doesn't return errors today, it might be nice to test anyway.

> +/*
> + * 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,
> +				 int isdir)
> +{
> +	u32 cookie = inotify_get_cookie();

How about deferring this atomic_inc() until it's known if the event is
going to be queued or not so that renames that have nothing to do with
inotify don't bounce the cache line around?  If it's *only* used to
assciate two different events with a single rename maybe it'd be better
to roll some compound rename event.

I hope this helps.  I, for one, would be thrilled to see dnotify deprecated.

- z

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-16 17:52 ` Zach Brown
@ 2005-06-16 18:25   ` Robert Love
  2005-06-17  1:30     ` Nick Piggin
  2005-06-17 17:07     ` [patch] inotify Arnd Bergmann
  0 siblings, 2 replies; 45+ messages in thread
From: Robert Love @ 2005-06-16 18:25 UTC (permalink / raw)
  To: Zach Brown; +Cc: linux-kernel, Al Viro, John McCutchan, Andrew Morton

On Thu, 2005-06-16 at 10:52 -0700, Zach Brown wrote:

> some quick comments, nothing earth shattering..

Thanks, zab!

> > + * struct inotify_kernel_event - An intofiy event, originating from a watch and
> 
> intofiy!

Fixed.

> > +/*
> > + * inotify_dev_is_watching_inode - is this device watching this inode?
> > + *
> > + * Callers must hold dev->sem.
> > + */
> > +static inline 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;
> > +}
> 
> Am I missing where this is used?

Nope.  I am not using it anymore.  Removed.

> > +       if (likely(!atomic_read(&inode->inotify_watch_count)))
> > +               return;
> 
> Are there still platforms that implement atomic_read() with locks?  I
> wonder if there isn't a cheaper way for inodes to find out that they're
> not involved in inotify.. maybe an inode function pointer that is only
> set to queue_event when watchers are around?

I don't know what esoteric architectures are doing, but the solution
needs to be atomic (or we need to say "we don't care about races"--but
its hard not to care about a pointer race).  On x86, at least, an
atomic_read() is trivial.

I actually would not mind being racey (in a safe way) or finding a
cheaper solution, especially if we could remove
inode->inotify_watch_count altogether (and not replace it with
anything).

But the overhead here is not biting us (we just went through some
off-list benchmarking that led to the inclusion of this check, in fact).

> > +	down(&inode->inotify_sem);
> > +	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
> > +		u32 watch_mask = watch->mask;
> > +		if (watch_mask & mask) {
> > +			struct inotify_device *dev = watch->dev;
> > +			get_inotify_watch(watch);
> > +			down(&dev->sem);
> > +			inotify_dev_queue_event(dev, watch, mask, cookie, name);
> 
> I wonder if its worth walking the watches as they're added and removed
> to calculate a total mask of what is being watched so that uninteresting
> events can be immediately ignored without walking the watches.

We used to do this.  I found it wasn't really worth it,
performance-wise.  And--I forget the details now--there was a locking
concern in caching the value.  Like, where I usually grab the locks
A->B, caching the mask led me to a B->A situation.  Or something.

> I also wonder about adding references to an event from each device that
> is watching the inode rather than queueing events on all the devices.
> If it only ever copies the full event to user space anyway, you'd just
> need a series of pointers on the dev that hold references to a
> refcounted event.  It looks like events are only ever dequeued from the
> device as they're walked from the head of the list so it doesn't seem
> like the ability to remove an event from the middle would be missed.
> 
> Too aggressive for a situation that rarely happens anyway?

_Probably_ way too aggressive for this situation, although I would not
rule it out.  This is always an optimization that we can do later, if
something proves expensive, but I don't see it right now.

> It'd be nice to get some wait_event_interruptible() love to replace the
> hand-rolled _wait()s and schedule().  It could probably be folded into
> the second loop, I imagine.  I suspect that the signal_pending() case
> wants to return ERESTARTSYS like w_e_i() does.

Yah, agreed.

> > +	nonseekable_open(inode, file);
> 
> While it doesn't return errors today, it might be nice to test anyway.

Sure.  Done.

> > +/*
> > + * 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,
> > +				 int isdir)
> > +{
> > +	u32 cookie = inotify_get_cookie();
> 
> How about deferring this atomic_inc() until it's known if the event is
> going to be queued or not so that renames that have nothing to do with
> inotify don't bounce the cache line around?  If it's *only* used to
> assciate two different events with a single rename maybe it'd be better
> to roll some compound rename event.

It is hard, because we don't know if the user is watching the src and
dst until we actually get there and see that they are--we'd have to walk
every watch to find if they match and then, if so, increment the cookie
and send it out to those two watches.

> I hope this helps.  I, for one, would be thrilled to see dnotify deprecated.

It helps a lot.  Thank you very much for the pair of eyes, Zach.

I appreciate it.

Updated patch below!

Best,

	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),
Gamin (a FAM replacement), and other projects.

See Documentation/filesystems/inotify.txt.

Signed-off-by: Robert Love <rml@novell.com>

 Documentation/filesystems/inotify.txt |  123 ++++
 fs/Kconfig                            |   13 
 fs/Makefile                           |    1 
 fs/attr.c                             |   33 -
 fs/compat.c                           |   12 
 fs/file_table.c                       |    3 
 fs/inode.c                            |    9 
 fs/inotify.c                          |  976 ++++++++++++++++++++++++++++++++++
 fs/namei.c                            |   30 -
 fs/nfsd/vfs.c                         |    6 
 fs/open.c                             |    4 
 fs/read_write.c                       |   15 
 fs/sysfs/file.c                       |    7 
 fs/xattr.c                            |    5 
 include/linux/fs.h                    |    7 
 include/linux/fsnotify.h              |  257 ++++++++
 include/linux/inotify.h               |  124 ++++
 include/linux/sched.h                 |    4 
 kernel/user.c                         |    4 
 19 files changed, 1569 insertions(+), 64 deletions(-)

diff -urN linux-2.6.12-rc6/Documentation/filesystems/inotify.txt linux/Documentation/filesystems/inotify.txt
--- linux-2.6.12-rc6/Documentation/filesystems/inotify.txt	1969-12-31 19:00:00.000000000 -0500
+++ linux/Documentation/filesystems/inotify.txt	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,123 @@
+				    inotify
+	     a powerful yet simple file change notification system
+
+
+
+Document started 15 Mar 2005 by Robert Love <rml@novell.com>
+
+(i) User Interface
+
+Inotify is controlled by a device node, /dev/inotify.  If you do not use udev,
+this device may need to be created manually.  First step, open it
+
+	int dev_fd = open ("/dev/inotify", O_RDONLY);
+
+Change events are managed by "watches".  A watch is an (object,mask) pair where
+the object is a file or directory and the mask is a bitmask of one or more
+inotify events that the application wishes to receive.  See <linux/inotify.h>
+for valid events.  A watch is referenced by a watch descriptor, or wd.
+
+Watches are added via a file descriptor.
+
+Watches on a directory will return events on any files inside of the directory.
+
+Adding a watch is simple,
+
+	/* 'wd' represents the watch on fd with mask */
+	struct inotify_request req = { fd, mask };
+	int wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+You can add a large number of files via something like
+
+	for each file to watch {
+		struct inotify_request req;
+		int file_fd;
+
+		file_fd = open (file, O_RDONLY);
+		if (fd < 0) {
+			perror ("open");
+			break;
+		}
+
+		req.fd = file_fd;
+		req.mask = mask;
+
+		wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+		close (fd);
+	}
+
+You can update an existing watch in the same manner, by passing in a new mask.
+
+An existing watch is removed via the INOTIFY_IGNORE ioctl, for example
+
+	ioctl (dev_fd, INOTIFY_IGNORE, wd);
+
+Events are provided in the form of an inotify_event structure that is read(2)
+from /dev/inotify.  The filename is of dynamic length and follows the struct.
+It is of size len.  The filename is padded with null bytes to ensure proper
+alignment.  This padding is reflected in len.
+
+You can slurp multiple events by passing a large buffer, for example
+
+	size_t len = read (fd, buf, BUF_LEN);
+
+Will return as many events as are available and fit in BUF_LEN.
+
+/dev/inotify is also select() and poll() able.
+
+You can find the size of the current event queue via the FIONREAD ioctl.
+
+All watches are destroyed and cleaned up on close.
+
+
+(ii) Internal Kernel Implementation
+
+Each open inotify device is associated with an inotify_device structure.
+
+Each watch is associated with an inotify_watch structure.  Watches are chained
+off of each associated device and each associated inode.
+
+See fs/inotify.c for the locking and lifetime rules.
+
+
+(iii) Rationale
+
+Q: What is the design decision behind not tying the watch to the
+open fd of the watched object?
+
+A: Watches are associated with an open inotify device, not an
+open file.  This solves the primary problem with dnotify:
+keeping the file open pins the file and thus, worse, pins the
+mount.  Dnotify is therefore infeasible for use on a desktop
+system with removable media as the media cannot be unmounted.
+
+Q: What is the design decision behind using an-fd-per-device as
+opposed to an fd-per-watch?
+
+A: An fd-per-watch quickly consumes more file descriptors than
+are allowed, more fd's than are feasible to manage, and more
+fd's than are ideally select()-able.  Yes, root can bump the
+per-process fd limit and yes, users can use epoll, but requiring
+both is silly and an extraneous requirement.  A watch consumes
+less memory than an open file, separating the number spaces is
+thus sensible.  The current design is what user-space developers
+want: Users open the device, once, and add n watches, requiring
+but one fd and no twiddling with fd limits.
+Opening /dev/inotify two thousand times is silly.  If we can
+implement user-space's preferences cleanly--and we can, the idr
+layer makes stuff like this trivial--then we should.
+
+Q: Why a device node?
+
+A: The second biggest problem with dnotify is that the user
+interface sucks ass.  Signals are a terrible, terrible interface
+for file notification.  Or for anything, for that matter.  The
+idea solution, from all perspectives, is a file descriptor based
+one that allows basic file I/O and poll/select.  Obtaining the
+fd and managing the watches could of been done either via a
+device file or a family of new system calls.  We decided to
+implement a device file because adding three or four new system
+calls that mirrored open, close, and ioctl seemed silly.  A
+character device makes sense from user-space and was easy to
+implement inside of the kernel.
diff -urN linux-2.6.12-rc6/fs/attr.c linux/fs/attr.c
--- linux-2.6.12-rc6/fs/attr.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/attr.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 @@
 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 @@
 	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.12-rc6/fs/compat.c linux/fs/compat.c
--- linux-2.6.12-rc6/fs/compat.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/compat.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 @@
 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 -urN linux-2.6.12-rc6/fs/file_table.c linux/fs/file_table.c
--- linux-2.6.12-rc6/fs/file_table.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/file_table.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 = {
@@ -123,6 +124,8 @@
 	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 -urN linux-2.6.12-rc6/fs/inode.c linux/fs/inode.c
--- linux-2.6.12-rc6/fs/inode.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/inode.c	2005-06-13 11:28:07.000000000 -0400
@@ -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:
@@ -128,6 +129,9 @@
 #ifdef CONFIG_QUOTA
 		memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));
 #endif
+#ifdef CONFIG_INOTIFY
+		atomic_set(&inode->inotify_watch_count, 0);
+#endif
 		inode->i_pipe = NULL;
 		inode->i_bdev = NULL;
 		inode->i_cdev = NULL;
@@ -202,6 +206,10 @@
 	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);
@@ -346,6 +354,7 @@
 
 	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);
 
diff -urN linux-2.6.12-rc6/fs/inotify.c linux/fs/inotify.c
--- linux-2.6.12-rc6/fs/inotify.c	1969-12-31 19:00:00.000000000 -0500
+++ linux/fs/inotify.c	2005-06-16 14:21:42.000000000 -0400
@@ -0,0 +1,976 @@
+/*
+ * 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/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 int max_user_devices;
+static int max_user_watches;
+static unsigned int 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 ssize_t show_max_queued_events(struct class_device *class, char *buf)
+{
+	return sprintf(buf, "%d\n", 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) {
+		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", 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) {
+		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", 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) {
+		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_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;
+
+	/* 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);
+}
+
+/*
+ * remove_kevent - cleans up and ultimately frees the given kevent
+ *
+ * Caller must hold dev->sem.
+ */
+static void remove_kevent(struct inotify_device *dev,
+			  struct inotify_kernel_event *kevent)
+{
+	list_del(&kevent->list);
+
+	dev->event_count--;
+	dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len;
+
+	kfree(kevent->name);
+	kmem_cache_free(event_cachep, kevent);
+}
+
+/*
+ * inotify_dev_event_dequeue - destroy an event on the given device
+ *
+ * Caller must hold dev->sem.
+ */
+static void inotify_dev_event_dequeue(struct inotify_device *dev)
+{
+	if (!list_empty(&dev->events)) {
+		struct inotify_kernel_event *kevent;
+		kevent = inotify_dev_get_event(dev);
+		remove_kevent(dev, kevent);
+	}
+}
+
+/*
+ * inotify_dev_get_wd - returns the next WD for use by the given dev
+ *
+ * Callers must hold dev->sem.  This function can sleep.
+ */
+static int inotify_dev_get_wd(struct inotify_device *dev,
+			      struct inotify_watch *watch)
+{
+	int ret;
+
+	do {
+		if (unlikely(!idr_pre_get(&dev->idr, GFP_KERNEL)))
+			return -ENOSPC;
+		ret = idr_get_new(&dev->idr, watch, &watch->wd);
+	} while (ret == -EAGAIN);
+
+	return ret;
+}
+
+/*
+ * create_watch - creates a watch on the given device.
+ *
+ * Callers must hold dev->sem.  Calls inotify_dev_get_wd() so may sleep.
+ * Both 'dev' and 'inode' (by way of nameidata) need to be pinned.
+ */
+static struct inotify_watch *create_watch(struct inotify_device *dev,
+					  u32 mask, struct inode *inode)
+{
+	struct inotify_watch *watch;
+	int ret;
+
+	if (atomic_read(&dev->user->inotify_watches) >= max_user_watches)
+		return ERR_PTR(-ENOSPC);
+
+	watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL);
+	if (unlikely(!watch))
+		return ERR_PTR(-ENOMEM);
+
+	ret = inotify_dev_get_wd(dev, watch);
+	if (unlikely(ret)) {
+		kmem_cache_free(watch_cachep, watch);
+		return ERR_PTR(ret);
+	}
+
+	watch->mask = mask;
+	atomic_set(&watch->count, 0);
+	INIT_LIST_HEAD(&watch->d_list);
+	INIT_LIST_HEAD(&watch->i_list);
+
+	/* save a reference to device and bump the count to make it official */
+	get_inotify_dev(dev);
+	watch->dev = dev;
+
+	/*
+	 * Save a reference to the inode and bump the ref count to make it
+	 * official.  We hold a reference to nameidata, which makes this safe.
+	 */
+	watch->inode = igrab(inode);
+
+	/* bump our own count, corresponding to our entry in dev->watches */
+	get_inotify_watch(watch);
+
+	atomic_inc(&dev->user->inotify_watches);
+	atomic_inc(&watch->inode->inotify_watch_count);
+
+	return watch;
+}
+
+/*
+ * inotify_find_dev - find the watch associated with the given inode and dev
+ *
+ * Callers must hold inode->inotify_sem.
+ */
+static struct inotify_watch *inode_find_dev(struct inode *inode,
+					    struct inotify_device *dev)
+{
+	struct inotify_watch *watch;
+
+	list_for_each_entry(watch, &inode->inotify_watches, i_list) {
+		if (watch->dev == dev)
+			return watch;
+	}
+
+	return NULL;
+}
+
+/*
+ * remove_watch_no_event - remove_watch() without the IN_IGNORED event.
+ */
+static void remove_watch_no_event(struct inotify_watch *watch,
+				  struct inotify_device *dev)
+{
+	list_del(&watch->i_list);
+	list_del(&watch->d_list);
+
+	atomic_dec(&watch->inode->inotify_watch_count);
+	atomic_dec(&dev->user->inotify_watches);
+	idr_remove(&dev->idr, watch->wd);
+	put_inotify_watch(watch);
+}
+
+/*
+ * remove_watch - Remove a watch from both the device and the inode.  Sends
+ * the IN_IGNORED event to the given device signifying that the inode is no
+ * longer watched.
+ *
+ * Callers must hold both inode->inotify_sem and dev->sem.  We drop a
+ * reference to the inode before returning.
+ *
+ * The inode is not iput() so as to remain atomic.  If the inode needs to be
+ * iput(), the call returns one.  Otherwise, it returns zero.
+ */
+static void remove_watch(struct inotify_watch *watch,struct inotify_device *dev)
+{
+	inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL);
+	remove_watch_no_event(watch, dev);
+}
+
+/* Kernel API */
+
+/**
+ * inotify_inode_queue_event - queue an event to all watches on this inode
+ * @inode: inode event is originating from
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
+			       const char *name)
+{
+	struct inotify_watch *watch, *next;
+
+	if (likely(!atomic_read(&inode->inotify_watch_count)))
+		return;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		u32 watch_mask = watch->mask;
+		if (watch_mask & mask) {
+			struct inotify_device *dev = watch->dev;
+			get_inotify_watch(watch);
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, mask, cookie, name);
+			if (watch_mask & IN_ONESHOT)
+				remove_watch_no_event(watch, dev);
+			up(&dev->sem);
+			put_inotify_watch(watch);
+		}
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_queue_event);
+
+/**
+ * inotify_dentry_parent_queue_event - queue an event to a dentry's parent
+ * @dentry: the dentry in question, we queue against this dentry's parent
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask,
+				       u32 cookie, const char *name)
+{
+	struct dentry *parent;
+	struct inode *inode;
+
+	spin_lock(&dentry->d_lock);
+	parent = dentry->d_parent;
+	inode = parent->d_inode;
+
+	if (unlikely(atomic_read(&inode->inotify_watch_count))) {
+		dget(parent);
+		spin_unlock(&dentry->d_lock);
+		inotify_inode_queue_event(inode, mask, cookie, name);
+		dput(parent);
+	} else
+		spin_unlock(&dentry->d_lock);
+}
+EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event);
+
+/**
+ * inotify_get_cookie - return a unique cookie for use in synchronizing events.
+ */
+u32 inotify_get_cookie(void)
+{
+	return atomic_inc_return(&inotify_cookie);
+}
+EXPORT_SYMBOL_GPL(inotify_get_cookie);
+
+/**
+ * inotify_unmount_inodes - an sb is unmounting.  handle any watched inodes.
+ * @list: list of inodes being unmounted (sb->s_inodes)
+ *
+ * Called with inode_lock held, protecting the unmounting super block's list
+ * of inodes, and with iprune_sem held, keeping shrink_icache_memory() at bay.
+ * We temporarily drop inode_lock, however, and CAN block.
+ */
+void inotify_unmount_inodes(struct list_head *list)
+{
+	struct inode *inode, *next_i;
+
+	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+		struct inotify_watch *watch, *next_w;
+		struct list_head *watches;
+
+		/*
+		 * We cannot __iget() an inode in state I_CLEAR, which is fine
+		 * as by that point the inode cannot have any watches.
+		 */
+		if (inode->i_state & I_CLEAR)
+			continue;
+
+		/* In case the remove_watch() drops a reference */
+		__iget(inode);
+
+		/*
+		 * We can safely drop inode_lock here because the per-sb list
+		 * of inodes must not change during unmount and iprune_sem
+		 * keeps shrink_icache_memory() away.
+		 */
+		spin_unlock(&inode_lock);
+
+		/* for each watch, send IN_UNMOUNT and then remove it */
+		down(&inode->inotify_sem);
+		watches = &inode->inotify_watches;
+		list_for_each_entry_safe(watch, next_w, watches, i_list) {
+			struct inotify_device *dev = watch->dev;
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, IN_UNMOUNT,0,NULL);
+			remove_watch(watch, dev);
+			up(&dev->sem);
+		}
+		up(&inode->inotify_sem);
+		iput(inode);		
+
+		spin_lock(&inode_lock);
+	}
+}
+EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
+
+/**
+ * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
+ * @inode: inode that is about to be removed
+ */
+void inotify_inode_is_dead(struct inode *inode)
+{
+	struct inotify_watch *watch, *next;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		struct inotify_device *dev = watch->dev;
+		down(&dev->sem);
+		remove_watch(watch, dev);
+		up(&dev->sem);
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_is_dead);
+
+/* Device Interface */
+
+static unsigned int inotify_poll(struct file *file, poll_table *wait)
+{
+	struct inotify_device *dev = file->private_data;
+	int ret = 0;
+
+	poll_wait(file, &dev->wq, wait);
+	down(&dev->sem);
+	if (!list_empty(&dev->events))
+		ret = POLLIN | POLLRDNORM;
+	up(&dev->sem);
+
+	return ret;
+}
+
+static ssize_t inotify_read(struct file *file, char __user *buf,
+			    size_t count, loff_t *pos)
+{
+	size_t event_size = sizeof (struct inotify_event);
+	struct inotify_device *dev;
+	char __user *start;
+	int ret;
+	DEFINE_WAIT(wait);
+
+	start = buf;
+	dev = file->private_data;
+
+	while (1) {
+		int events;
+
+		prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
+
+		down(&dev->sem);
+		events = !list_empty(&dev->events);
+		up(&dev->sem);
+		if (events) {
+			ret = 0;
+			break;
+		}
+
+		if (file->f_flags & O_NONBLOCK) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			ret = -EINTR;
+			break;
+		}
+
+		schedule();
+	}
+
+	finish_wait(&dev->wq, &wait);
+	if (ret)
+		return ret;
+
+	down(&dev->sem);
+	while (1) {
+		struct inotify_kernel_event *kevent;
+
+		ret = buf - start;
+		if (list_empty(&dev->events))
+			break;
+
+		kevent = inotify_dev_get_event(dev);
+		if (event_size + kevent->event.len > count)
+			break;
+
+		if (copy_to_user(buf, &kevent->event, event_size)) {
+			ret = -EFAULT;
+			break;
+		}
+		buf += event_size;
+		count -= event_size;
+
+		if (kevent->name) {
+			if (copy_to_user(buf, kevent->name, kevent->event.len)){
+				ret = -EFAULT;
+				break;
+			}
+			buf += kevent->event.len;
+			count -= kevent->event.len;
+		}
+
+		remove_kevent(dev, kevent);
+	}
+	up(&dev->sem);
+
+	return ret;
+}
+
+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 (unlikely(atomic_read(&user->inotify_devs) >= max_user_devices)) {
+		ret = -EMFILE;
+		goto out_err;
+	}
+
+	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	if (unlikely(!dev)) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	idr_init(&dev->idr);
+	INIT_LIST_HEAD(&dev->events);
+	INIT_LIST_HEAD(&dev->watches);
+	init_waitqueue_head(&dev->wq);
+	sema_init(&dev->sem, 1);
+	dev->event_count = 0;
+	dev->queue_size = 0;
+	dev->max_events = max_queued_events;
+	dev->user = user;
+	atomic_set(&dev->count, 0);
+
+	get_inotify_dev(dev);
+	atomic_inc(&user->inotify_devs);
+
+	file->private_data = dev;
+
+	ret = nonseekable_open(inode, file);
+	if (ret)
+		goto out_err;
+
+	return 0;
+out_err:
+	free_uid(user);
+	return ret;
+}
+
+static int inotify_release(struct inode *ignored, struct file *file)
+{
+	struct inotify_device *dev = file->private_data;
+
+	/*
+	 * Destroy all of the watches on this device.  Unfortunately, not very
+	 * pretty.  We cannot do a simple iteration over the list, because we
+	 * do not know the inode until we iterate to the watch.  But we need to
+	 * hold inode->inotify_sem before dev->sem.  The following works.
+	 */
+	while (1) {
+		struct inotify_watch *watch;
+		struct list_head *watches;
+		struct inode *inode;
+
+		down(&dev->sem);
+		watches = &dev->watches;
+		if (list_empty(watches)) {
+			up(&dev->sem);
+			break;
+		}
+		watch = list_entry(watches->next, struct inotify_watch, d_list);
+		get_inotify_watch(watch);
+		up(&dev->sem);
+
+		inode = watch->inode;
+		down(&inode->inotify_sem);
+		down(&dev->sem);
+		remove_watch_no_event(watch, dev);
+		up(&dev->sem);
+		up(&inode->inotify_sem);
+		put_inotify_watch(watch);
+	}
+
+	/* destroy all of the events on this device */
+	down(&dev->sem);
+	while (!list_empty(&dev->events))
+		inotify_dev_event_dequeue(dev);
+	up(&dev->sem);
+
+	/* free this device: the put matching the get in inotify_open() */
+	put_inotify_dev(dev);
+
+	return 0;
+}
+
+static int inotify_add_watch(struct inotify_device *dev, int fd, u32 mask)
+{
+	struct inotify_watch *watch, *old;
+	struct inode *inode;
+	struct file *filp;
+	int ret;
+
+	filp = fget(fd);
+	if (!filp)
+		return -EBADF;
+	inode = filp->f_dentry->d_inode;
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* don't let user-space set invalid bits: we don't want flags set */
+	mask &= IN_ALL_EVENTS;
+	if (!mask) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Handle the case of re-adding a watch on an (inode,dev) pair that we
+	 * are already watching.  We just update the mask and return its wd.
+	 */
+	old = inode_find_dev(inode, dev);
+	if (unlikely(old)) {
+		old->mask = mask;
+		ret = old->wd;
+		goto out;
+	}
+
+	watch = create_watch(dev, mask, inode);
+	if (unlikely(IS_ERR(watch))) {
+		ret = PTR_ERR(watch);
+		goto out;
+	}
+
+	/* Add the watch to the device's and the inode's list */
+	list_add(&watch->d_list, &dev->watches);
+	list_add(&watch->i_list, &inode->inotify_watches);
+	ret = watch->wd;
+
+out:
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	fput(filp);
+
+	return ret;
+}
+
+/*
+ * inotify_ignore - handle the INOTIFY_IGNORE ioctl, asking that a given wd be
+ * removed from the device.
+ *
+ * Can sleep.
+ */
+static int inotify_ignore(struct inotify_device *dev, s32 wd)
+{
+	struct inotify_watch *watch;
+	struct inode *inode;
+
+	down(&dev->sem);
+	watch = idr_find(&dev->idr, wd);
+	if (unlikely(!watch)) {
+		up(&dev->sem);
+		return -EINVAL;
+	}
+	get_inotify_watch(watch);
+	inode = watch->inode;
+	up(&dev->sem);
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* make sure that we did not race */
+	watch = idr_find(&dev->idr, wd);
+	if (likely(watch))
+		remove_watch(watch, dev);
+
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	put_inotify_watch(watch);
+
+	return 0;
+}
+
+static long inotify_ioctl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	struct inotify_device *dev;
+	struct inotify_watch_request request;
+	void __user *p;
+	int ret = -ENOTTY;
+	s32 wd;
+
+	dev = file->private_data;
+	p = (void __user *) arg;
+
+	switch (cmd) {
+	case INOTIFY_WATCH:
+		if (unlikely(copy_from_user(&request, p, sizeof (request)))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_add_watch(dev, request.fd, request.mask);
+		break;
+	case INOTIFY_IGNORE:
+		if (unlikely(get_user(wd, (int __user *) p))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_ignore(dev, wd);
+		break;
+	case FIONREAD:
+		ret = put_user(dev->queue_size, (int __user *) p);
+		break;
+	}
+
+	return ret;
+}
+
+static struct file_operations inotify_fops = {
+	.owner		= THIS_MODULE,
+	.poll		= inotify_poll,
+	.read		= inotify_read,
+	.open		= inotify_open,
+	.release	= inotify_release,
+	.unlocked_ioctl	= inotify_ioctl,
+	.compat_ioctl	= inotify_ioctl,
+};
+
+static struct miscdevice inotify_device = {
+	.minor  = MISC_DYNAMIC_MINOR,
+	.name	= "inotify",
+	.fops	= &inotify_fops,
+};
+
+/*
+ * inotify_init - Our initialization function.  Note that we cannnot return
+ * error because we have compiled-in VFS hooks.  So an (unlikely) failure here
+ * must result in panic().
+ */
+static int __init inotify_init(void)
+{
+	struct class_device *class;
+	int ret;
+
+	ret = misc_register(&inotify_device);
+	if (unlikely(ret))
+		panic("inotify: misc_register returned %d\n", ret);
+
+	max_queued_events = 8192;
+	max_user_devices = 128;
+	max_user_watches = 8192;
+
+	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);
+
+	printk(KERN_INFO "inotify device minor=%d\n", inotify_device.minor);
+
+	return 0;
+}
+
+module_init(inotify_init);
diff -urN linux-2.6.12-rc6/fs/Kconfig linux/fs/Kconfig
--- linux-2.6.12-rc6/fs/Kconfig	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/Kconfig	2005-06-13 11:28:04.000000000 -0400
@@ -339,6 +339,19 @@
 	  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 -urN linux-2.6.12-rc6/fs/Makefile linux/fs/Makefile
--- linux-2.6.12-rc6/fs/Makefile	2005-03-02 02:38:10.000000000 -0500
+++ linux/fs/Makefile	2005-06-13 11:28:04.000000000 -0400
@@ -11,6 +11,7 @@
 		attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \
 
+obj-$(CONFIG_INOTIFY)		+= inotify.o
 obj-$(CONFIG_EPOLL)		+= eventpoll.o
 obj-$(CONFIG_COMPAT)		+= compat.o
 
diff -urN linux-2.6.12-rc6/fs/namei.c linux/fs/namei.c
--- linux-2.6.12-rc6/fs/namei.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/namei.c	2005-06-13 11:28:04.000000000 -0400
@@ -21,7 +21,7 @@
 #include <linux/namei.h>
 #include <linux/quotaops.h>
 #include <linux/pagemap.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
 #include <linux/security.h>
@@ -1296,7 +1296,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;
@@ -1602,7 +1602,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;
@@ -1675,7 +1675,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;
@@ -1766,7 +1766,7 @@
 	}
 	up(&dentry->d_inode->i_sem);
 	if (!error) {
-		inode_dir_notify(dir, DN_DELETE);
+		fsnotify_rmdir(dentry, dentry->d_inode, dir);
 		d_delete(dentry);
 	}
 	dput(dentry);
@@ -1839,9 +1839,10 @@
 
 	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
 	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
+		fsnotify_unlink(dentry, dir);
 		d_delete(dentry);
-		inode_dir_notify(dir, DN_DELETE);
 	}
+
 	return error;
 }
 
@@ -1915,7 +1916,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;
@@ -1988,7 +1989,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;
@@ -2152,6 +2153,7 @@
 {
 	int error;
 	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	const char *old_name;
 
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
@@ -2173,18 +2175,18 @@
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
 
+	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
+
 	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, is_dir);
 	}
+	fsnotify_oldname_free(old_name);
+
 	return error;
 }
 
diff -urN linux-2.6.12-rc6/fs/nfsd/vfs.c linux/fs/nfsd/vfs.c
--- linux-2.6.12-rc6/fs/nfsd/vfs.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/nfsd/vfs.c	2005-06-13 11:28:04.000000000 -0400
@@ -45,7 +45,7 @@
 #endif /* CONFIG_NFSD_V3 */
 #include <linux/nfsd/nfsfh.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #ifdef CONFIG_NFSD_V4
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
@@ -861,7 +861,7 @@
 		nfsdstats.io_read += err;
 		*count = err;
 		err = 0;
-		dnotify_parent(file->f_dentry, DN_ACCESS);
+		fsnotify_access(file->f_dentry);
 	} else 
 		err = nfserrno(err);
 out:
@@ -917,7 +917,7 @@
 	set_fs(oldfs);
 	if (err >= 0) {
 		nfsdstats.io_write += cnt;
-		dnotify_parent(file->f_dentry, DN_MODIFY);
+		fsnotify_modify(file->f_dentry);
 	}
 
 	/* clear setuid/setgid flag after write */
diff -urN linux-2.6.12-rc6/fs/open.c linux/fs/open.c
--- linux-2.6.12-rc6/fs/open.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/open.c	2005-06-13 11:28:04.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/smp_lock.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
@@ -944,9 +944,11 @@
 		fd = get_unused_fd();
 		if (fd >= 0) {
 			struct file *f = filp_open(tmp, flags, mode);
+
 			error = PTR_ERR(f);
 			if (IS_ERR(f))
 				goto out_error;
+			fsnotify_open(f->f_dentry);
 			fd_install(fd, f);
 		}
 out:
diff -urN linux-2.6.12-rc6/fs/read_write.c linux/fs/read_write.c
--- linux-2.6.12-rc6/fs/read_write.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/read_write.c	2005-06-13 11:28:04.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/security.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
@@ -239,7 +239,7 @@
 			else
 				ret = do_sync_read(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_ACCESS);
+				fsnotify_access(file->f_dentry);
 				current->rchar += ret;
 			}
 			current->syscr++;
@@ -287,7 +287,7 @@
 			else
 				ret = do_sync_write(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_MODIFY);
+				fsnotify_modify(file->f_dentry);
 				current->wchar += ret;
 			}
 			current->syscw++;
@@ -523,9 +523,12 @@
 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) {
+		if (type == READ)
+			fsnotify_access(file->f_dentry);
+		else
+			fsnotify_modify(file->f_dentry);
+	}
 	return ret;
 Efault:
 	ret = -EFAULT;
diff -urN linux-2.6.12-rc6/fs/sysfs/file.c linux/fs/sysfs/file.c
--- linux-2.6.12-rc6/fs/sysfs/file.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/sysfs/file.c	2005-06-13 11:28:04.000000000 -0400
@@ -3,7 +3,7 @@
  */
 
 #include <linux/module.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/kobject.h>
 #include <asm/uaccess.h>
 #include <asm/semaphore.h>
@@ -389,9 +389,6 @@
  * sysfs_update_file - update the modified timestamp on an object attribute.
  * @kobj: object we're acting for.
  * @attr: attribute descriptor.
- *
- * Also call dnotify for the dentry, which lots of userspace programs
- * use.
  */
 int sysfs_update_file(struct kobject * kobj, const struct attribute * attr)
 {
@@ -406,7 +403,7 @@
 		if (victim->d_inode && 
 		    (victim->d_parent->d_inode == dir->d_inode)) {
 			victim->d_inode->i_mtime = CURRENT_TIME;
-			dnotify_parent(victim, DN_MODIFY);
+			fsnotify_modify(victim);
 
 			/**
 			 * Drop reference from initial sysfs_get_dentry().
diff -urN linux-2.6.12-rc6/fs/xattr.c linux/fs/xattr.c
--- linux-2.6.12-rc6/fs/xattr.c	2005-03-02 02:38:07.000000000 -0500
+++ linux/fs/xattr.c	2005-06-13 11:28:04.000000000 -0400
@@ -16,6 +16,7 @@
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/module.h>
+#include <linux/fsnotify.h>
 #include <asm/uaccess.h>
 
 /*
@@ -57,8 +58,10 @@
 		if (error)
 			goto out;
 		error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
-		if (!error)
+		if (!error) {
+			fsnotify_xattr(d);
 			security_inode_post_setxattr(d, kname, kvalue, size, flags);
+		}
 out:
 		up(&d->d_inode->i_sem);
 	}
diff -urN linux-2.6.12-rc6/include/linux/fs.h linux/include/linux/fs.h
--- linux-2.6.12-rc6/include/linux/fs.h	2005-06-07 15:47:53.000000000 -0400
+++ linux/include/linux/fs.h	2005-06-13 11:28:07.000000000 -0400
@@ -471,6 +471,12 @@
 	struct dnotify_struct	*i_dnotify; /* for directory notifications */
 #endif
 
+#ifdef CONFIG_INOTIFY
+	atomic_t		inotify_watch_count; /* number of watches */
+	struct list_head	inotify_watches; /* watches on this inode */
+	struct semaphore	inotify_sem;	/* protects the watches list */
+#endif
+
 	unsigned long		i_state;
 	unsigned long		dirtied_when;	/* jiffies of first dirtying */
 
@@ -1369,7 +1375,6 @@
 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 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.12-rc6/include/linux/fsnotify.h linux/include/linux/fsnotify.h
--- linux-2.6.12-rc6/include/linux/fsnotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/fsnotify.h	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,257 @@
+#ifndef _LINUX_FS_NOTIFY_H
+#define _LINUX_FS_NOTIFY_H
+
+/*
+ * include/linux/fsnotify.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 <linux/dnotify.h>
+#include <linux/inotify.h>
+
+/*
+ * 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,
+				 int isdir)
+{
+	u32 cookie = inotify_get_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);
+	}
+
+	if (isdir)
+		isdir = IN_ISDIR;
+	inotify_inode_queue_event(old_dir, IN_MOVED_FROM|isdir,cookie,old_name);
+	inotify_inode_queue_event(new_dir, IN_MOVED_TO|isdir, cookie, new_name);
+}
+
+/*
+ * fsnotify_unlink - file was unlinked
+ */
+static inline void fsnotify_unlink(struct dentry *dentry, struct inode *dir)
+{
+	struct inode *inode = dentry->d_inode;
+
+	inode_dir_notify(dir, DN_DELETE);
+	inotify_inode_queue_event(dir, IN_DELETE, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL);
+
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * 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|IN_ISDIR,0,dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF | IN_ISDIR, 0, NULL);
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * fsnotify_create - 'name' was linked in
+ */
+static inline void fsnotify_create(struct inode *inode, const char *name)
+{
+	inode_dir_notify(inode, DN_CREATE);
+	inotify_inode_queue_event(inode, IN_CREATE, 0, name);
+}
+
+/*
+ * 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 | IN_ISDIR, 0, name);
+}
+
+/*
+ * fsnotify_access - file was read
+ */
+static inline void fsnotify_access(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ACCESS;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_ACCESS);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_modify - file was modified
+ */
+static inline void fsnotify_modify(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_MODIFY;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_MODIFY);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_open - file was opened
+ */
+static inline void fsnotify_open(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_OPEN;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+}
+
+/*
+ * fsnotify_close - file was closed
+ */
+static inline void fsnotify_close(struct file *file)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+	mode_t mode = file->f_mode;
+	u32 mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_xattr - extended attributes were changed
+ */
+static inline void fsnotify_xattr(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ATTRIB;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	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)
+{
+	struct inode *inode = dentry->d_inode;
+	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) {
+		if (S_ISDIR(inode->i_mode))
+			in_mask |= IN_ISDIR;
+		inotify_inode_queue_event(inode, in_mask, 0, NULL);
+		inotify_dentry_parent_queue_event(dentry, in_mask, 0,
+						  dentry->d_name.name);
+	}
+}
+
+#ifdef CONFIG_INOTIFY	/* inotify helpers */
+
+/*
+ * fsnotify_oldname_init - save off the old filename before we change it
+ *
+ * XXX: This could be kstrdup if only we could add that to lib/string.c
+ */
+static inline const char *fsnotify_oldname_init(const char *name)
+{
+	size_t len;
+	char *buf;
+
+	len = strlen(name) + 1;
+	buf = kmalloc(len, GFP_KERNEL);
+	if (likely(buf))
+		memcpy(buf, name, len);
+	return buf;
+}
+
+/*
+ * 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 const char *fsnotify_oldname_init(const char *name)
+{
+	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.12-rc6/include/linux/inotify.h linux/include/linux/inotify.h
--- linux-2.6.12-rc6/include/linux/inotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/inotify.h	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,124 @@
+/*
+ * Inode based directory notification for Linux
+ *
+ * Copyright (C) 2005 John McCutchan
+ */
+
+#ifndef _LINUX_INOTIFY_H
+#define _LINUX_INOTIFY_H
+
+#include <linux/types.h>
+
+/*
+ * 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 to synchronize two events */
+	__u32		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 {
+	int		fd;		/* fd of filename to watch */
+	__u32		mask;		/* event mask */
+};
+
+/* the following are legal, implemented events that user-space can watch for */
+#define IN_ACCESS		0x00000001	/* File was accessed */
+#define IN_MODIFY		0x00000002	/* File was modified */
+#define IN_ATTRIB		0x00000004	/* Metadata changed */
+#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_CREATE		0x00000100	/* Subfile was created */
+#define IN_DELETE		0x00000200	/* Subfile was deleted */
+#define IN_DELETE_SELF		0x00000400	/* Self was deleted */
+
+/* the following are legal events.  they are sent as needed to any watch */
+#define IN_UNMOUNT		0x00002000	/* Backing fs was unmounted */
+#define IN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
+#define IN_IGNORED		0x00008000	/* File was ignored */
+
+/* helper events */
+#define IN_CLOSE		(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */
+#define IN_MOVE			(IN_MOVED_FROM | IN_MOVED_TO) /* moves */
+
+/* special flags */
+#define IN_ISDIR		0x40000000	/* event occurred against dir */
+#define IN_ONESHOT		0x80000000	/* only send event once */
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define IN_ALL_EVENTS	(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
+			 IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \
+			 IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF)
+
+#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 <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/config.h>
+
+#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_unmount_inodes(struct list_head *);
+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_unmount_inodes(struct list_head *list)
+{
+}
+
+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.12-rc6/include/linux/sched.h linux/include/linux/sched.h
--- linux-2.6.12-rc6/include/linux/sched.h	2005-06-07 15:47:53.000000000 -0400
+++ linux/include/linux/sched.h	2005-06-13 11:28:04.000000000 -0400
@@ -404,6 +404,10 @@
 	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? */
+#ifdef CONFIG_INOTIFY
+	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? */
+#endif
 	/* 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.12-rc6/kernel/user.c linux/kernel/user.c
--- linux-2.6.12-rc6/kernel/user.c	2005-06-07 15:47:53.000000000 -0400
+++ linux/kernel/user.c	2005-06-13 11:28:04.000000000 -0400
@@ -120,6 +120,10 @@
 		atomic_set(&new->processes, 0);
 		atomic_set(&new->files, 0);
 		atomic_set(&new->sigpending, 0);
+#ifdef CONFIG_INOTIFY
+		atomic_set(&new->inotify_watches, 0);
+		atomic_set(&new->inotify_devs, 0);
+#endif
 
 		new->mq_bytes = 0;
 		new->locked_shm = 0;



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-16 18:25   ` Robert Love
@ 2005-06-17  1:30     ` Nick Piggin
  2005-06-17  1:35       ` Robert Love
  2005-06-17 17:07     ` [patch] inotify Arnd Bergmann
  1 sibling, 1 reply; 45+ messages in thread
From: Nick Piggin @ 2005-06-17  1:30 UTC (permalink / raw)
  To: Robert Love
  Cc: Zach Brown, linux-kernel, Al Viro, John McCutchan, Andrew Morton

Robert Love wrote:
> On Thu, 2005-06-16 at 10:52 -0700, Zach Brown wrote:

>>>+       if (likely(!atomic_read(&inode->inotify_watch_count)))
>>>+               return;
>>
>>Are there still platforms that implement atomic_read() with locks?  I
>>wonder if there isn't a cheaper way for inodes to find out that they're
>>not involved in inotify.. maybe an inode function pointer that is only
>>set to queue_event when watchers are around?
> 
> 
> I don't know what esoteric architectures are doing, but the solution
> needs to be atomic (or we need to say "we don't care about races"--but
> its hard not to care about a pointer race).  On x86, at least, an
> atomic_read() is trivial.
> 
> I actually would not mind being racey (in a safe way) or finding a
> cheaper solution, especially if we could remove
> inode->inotify_watch_count altogether (and not replace it with
> anything).
> 
> But the overhead here is not biting us (we just went through some
> off-list benchmarking that led to the inclusion of this check, in fact).
> 

What we could do is just check list_empty(&inode->inotify_watchers)
and remove the atomic count completely.

We don't actually care about getting an exact count at all, just
whether or not it is empty, and in that case using list_empty is
no more racy than checking an atomic count, both are done outside
any locks.

It is basically just a lock avoidance heuristic. But I think count
is superfluous - off with its head!

-- 
SUSE Labs, Novell Inc.

Send instant messages to your online friends http://au.messenger.yahoo.com 

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17  1:30     ` Nick Piggin
@ 2005-06-17  1:35       ` Robert Love
  2005-06-17 15:15         ` [patch] inotify, improved Robert Love
  0 siblings, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-06-17  1:35 UTC (permalink / raw)
  To: Nick Piggin
  Cc: Zach Brown, linux-kernel, Al Viro, John McCutchan, Andrew Morton

On Fri, 2005-06-17 at 11:30 +1000, Nick Piggin wrote:

> What we could do is just check list_empty(&inode->inotify_watchers)
> and remove the atomic count completely.
> 
> We don't actually care about getting an exact count at all, just
> whether or not it is empty, and in that case using list_empty is
> no more racy than checking an atomic count, both are done outside
> any locks.
> 
> It is basically just a lock avoidance heuristic. But I think count
> is superfluous - off with its head!

Yah.  This is what I originally did.  Because of the current
implementation of list_empty(), the race is never dangerous (e.g., no
segfault), we just get false positives/negatives, but that is no
different than the atomic check, really, since we can race in the
interim and then we go on and check the list anyhow.

So, let's do it.  I'll cook up a patch and updated inotify tomorrow, if
no one beats me to the punch.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* [patch] inotify, improved.
  2005-06-17  1:35       ` Robert Love
@ 2005-06-17 15:15         ` Robert Love
  2005-06-17 15:37           ` Chris Friesen
  2005-06-17 17:20           ` Zach Brown
  0 siblings, 2 replies; 45+ messages in thread
From: Robert Love @ 2005-06-17 15:15 UTC (permalink / raw)
  To: Nick Piggin
  Cc: Zach Brown, linux-kernel, Al Viro, John McCutchan, Andrew Morton

[-- Attachment #1: Type: text/plain, Size: 572 bytes --]

On Thu, 2005-06-16 at 21:35 -0400, Robert Love wrote:

> Yah.  This is what I originally did.  Because of the current
> implementation of list_empty(), the race is never dangerous (e.g., no
> segfault), we just get false positives/negatives, but that is no
> different than the atomic check, really, since we can race in the
> interim and then we go on and check the list anyhow.
> 
> So, let's do it.  I'll cook up a patch and updated inotify tomorrow, if
> no one beats me to the punch.

Attached patch removes the atomics and does a lockless list check.

	Robert Love


[-- Attachment #2: inotify-0.23-rml-2.6.12-rc6-12.patch --]
[-- Type: text/x-patch, Size: 59059 bytes --]

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),
Gamin (a FAM replacement), and other projects.

See Documentation/filesystems/inotify.txt.

Signed-off-by: Robert Love <rml@novell.com>

 Documentation/filesystems/inotify.txt |  123 ++++
 fs/Kconfig                            |   13 
 fs/Makefile                           |    1 
 fs/attr.c                             |   33 -
 fs/compat.c                           |   12 
 fs/file_table.c                       |    3 
 fs/inode.c                            |    9 
 fs/inotify.c                          |  983 ++++++++++++++++++++++++++++++++++
 fs/namei.c                            |   30 -
 fs/nfsd/vfs.c                         |    6 
 fs/open.c                             |    4 
 fs/read_write.c                       |   15 
 fs/sysfs/file.c                       |    7 
 fs/xattr.c                            |    5 
 include/linux/fs.h                    |    6 
 include/linux/fsnotify.h              |  257 ++++++++
 include/linux/inotify.h               |  124 ++++
 include/linux/sched.h                 |    4 
 kernel/user.c                         |    4 
 19 files changed, 1575 insertions(+), 64 deletions(-)

diff -urN linux-2.6.12-rc6/Documentation/filesystems/inotify.txt linux/Documentation/filesystems/inotify.txt
--- linux-2.6.12-rc6/Documentation/filesystems/inotify.txt	1969-12-31 19:00:00.000000000 -0500
+++ linux/Documentation/filesystems/inotify.txt	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,123 @@
+				    inotify
+	     a powerful yet simple file change notification system
+
+
+
+Document started 15 Mar 2005 by Robert Love <rml@novell.com>
+
+(i) User Interface
+
+Inotify is controlled by a device node, /dev/inotify.  If you do not use udev,
+this device may need to be created manually.  First step, open it
+
+	int dev_fd = open ("/dev/inotify", O_RDONLY);
+
+Change events are managed by "watches".  A watch is an (object,mask) pair where
+the object is a file or directory and the mask is a bitmask of one or more
+inotify events that the application wishes to receive.  See <linux/inotify.h>
+for valid events.  A watch is referenced by a watch descriptor, or wd.
+
+Watches are added via a file descriptor.
+
+Watches on a directory will return events on any files inside of the directory.
+
+Adding a watch is simple,
+
+	/* 'wd' represents the watch on fd with mask */
+	struct inotify_request req = { fd, mask };
+	int wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+You can add a large number of files via something like
+
+	for each file to watch {
+		struct inotify_request req;
+		int file_fd;
+
+		file_fd = open (file, O_RDONLY);
+		if (fd < 0) {
+			perror ("open");
+			break;
+		}
+
+		req.fd = file_fd;
+		req.mask = mask;
+
+		wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+		close (fd);
+	}
+
+You can update an existing watch in the same manner, by passing in a new mask.
+
+An existing watch is removed via the INOTIFY_IGNORE ioctl, for example
+
+	ioctl (dev_fd, INOTIFY_IGNORE, wd);
+
+Events are provided in the form of an inotify_event structure that is read(2)
+from /dev/inotify.  The filename is of dynamic length and follows the struct.
+It is of size len.  The filename is padded with null bytes to ensure proper
+alignment.  This padding is reflected in len.
+
+You can slurp multiple events by passing a large buffer, for example
+
+	size_t len = read (fd, buf, BUF_LEN);
+
+Will return as many events as are available and fit in BUF_LEN.
+
+/dev/inotify is also select() and poll() able.
+
+You can find the size of the current event queue via the FIONREAD ioctl.
+
+All watches are destroyed and cleaned up on close.
+
+
+(ii) Internal Kernel Implementation
+
+Each open inotify device is associated with an inotify_device structure.
+
+Each watch is associated with an inotify_watch structure.  Watches are chained
+off of each associated device and each associated inode.
+
+See fs/inotify.c for the locking and lifetime rules.
+
+
+(iii) Rationale
+
+Q: What is the design decision behind not tying the watch to the
+open fd of the watched object?
+
+A: Watches are associated with an open inotify device, not an
+open file.  This solves the primary problem with dnotify:
+keeping the file open pins the file and thus, worse, pins the
+mount.  Dnotify is therefore infeasible for use on a desktop
+system with removable media as the media cannot be unmounted.
+
+Q: What is the design decision behind using an-fd-per-device as
+opposed to an fd-per-watch?
+
+A: An fd-per-watch quickly consumes more file descriptors than
+are allowed, more fd's than are feasible to manage, and more
+fd's than are ideally select()-able.  Yes, root can bump the
+per-process fd limit and yes, users can use epoll, but requiring
+both is silly and an extraneous requirement.  A watch consumes
+less memory than an open file, separating the number spaces is
+thus sensible.  The current design is what user-space developers
+want: Users open the device, once, and add n watches, requiring
+but one fd and no twiddling with fd limits.
+Opening /dev/inotify two thousand times is silly.  If we can
+implement user-space's preferences cleanly--and we can, the idr
+layer makes stuff like this trivial--then we should.
+
+Q: Why a device node?
+
+A: The second biggest problem with dnotify is that the user
+interface sucks ass.  Signals are a terrible, terrible interface
+for file notification.  Or for anything, for that matter.  The
+idea solution, from all perspectives, is a file descriptor based
+one that allows basic file I/O and poll/select.  Obtaining the
+fd and managing the watches could of been done either via a
+device file or a family of new system calls.  We decided to
+implement a device file because adding three or four new system
+calls that mirrored open, close, and ioctl seemed silly.  A
+character device makes sense from user-space and was easy to
+implement inside of the kernel.
diff -urN linux-2.6.12-rc6/fs/attr.c linux/fs/attr.c
--- linux-2.6.12-rc6/fs/attr.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/attr.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 @@
 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 @@
 	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.12-rc6/fs/compat.c linux/fs/compat.c
--- linux-2.6.12-rc6/fs/compat.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/compat.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 @@
 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 -urN linux-2.6.12-rc6/fs/file_table.c linux/fs/file_table.c
--- linux-2.6.12-rc6/fs/file_table.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/file_table.c	2005-06-13 11:28:04.000000000 -0400
@@ -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 = {
@@ -123,6 +124,8 @@
 	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 -urN linux-2.6.12-rc6/fs/inode.c linux/fs/inode.c
--- linux-2.6.12-rc6/fs/inode.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/inode.c	2005-06-13 11:28:07.000000000 -0400
@@ -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:
@@ -128,6 +129,9 @@
 #ifdef CONFIG_QUOTA
 		memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));
 #endif
+#ifdef CONFIG_INOTIFY
+		atomic_set(&inode->inotify_watch_count, 0);
+#endif
 		inode->i_pipe = NULL;
 		inode->i_bdev = NULL;
 		inode->i_cdev = NULL;
@@ -202,6 +206,10 @@
 	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);
@@ -346,6 +354,7 @@
 
 	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);
 
diff -urN linux-2.6.12-rc6/fs/inotify.c linux/fs/inotify.c
--- linux-2.6.12-rc6/fs/inotify.c	1969-12-31 19:00:00.000000000 -0500
+++ linux/fs/inotify.c	2005-06-17 11:07:19.000000000 -0400
@@ -0,0 +1,983 @@
+/*
+ * 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/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 int max_user_devices;
+static int max_user_watches;
+static unsigned int 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 ssize_t show_max_queued_events(struct class_device *class, char *buf)
+{
+	return sprintf(buf, "%d\n", 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) {
+		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", 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) {
+		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", 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) {
+		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_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;
+
+	/* 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);
+}
+
+/*
+ * remove_kevent - cleans up and ultimately frees the given kevent
+ *
+ * Caller must hold dev->sem.
+ */
+static void remove_kevent(struct inotify_device *dev,
+			  struct inotify_kernel_event *kevent)
+{
+	list_del(&kevent->list);
+
+	dev->event_count--;
+	dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len;
+
+	kfree(kevent->name);
+	kmem_cache_free(event_cachep, kevent);
+}
+
+/*
+ * inotify_dev_event_dequeue - destroy an event on the given device
+ *
+ * Caller must hold dev->sem.
+ */
+static void inotify_dev_event_dequeue(struct inotify_device *dev)
+{
+	if (!list_empty(&dev->events)) {
+		struct inotify_kernel_event *kevent;
+		kevent = inotify_dev_get_event(dev);
+		remove_kevent(dev, kevent);
+	}
+}
+
+/*
+ * inotify_dev_get_wd - returns the next WD for use by the given dev
+ *
+ * Callers must hold dev->sem.  This function can sleep.
+ */
+static int inotify_dev_get_wd(struct inotify_device *dev,
+			      struct inotify_watch *watch)
+{
+	int ret;
+
+	do {
+		if (unlikely(!idr_pre_get(&dev->idr, GFP_KERNEL)))
+			return -ENOSPC;
+		ret = idr_get_new(&dev->idr, watch, &watch->wd);
+	} while (ret == -EAGAIN);
+
+	return ret;
+}
+
+/*
+ * create_watch - creates a watch on the given device.
+ *
+ * Callers must hold dev->sem.  Calls inotify_dev_get_wd() so may sleep.
+ * Both 'dev' and 'inode' (by way of nameidata) need to be pinned.
+ */
+static struct inotify_watch *create_watch(struct inotify_device *dev,
+					  u32 mask, struct inode *inode)
+{
+	struct inotify_watch *watch;
+	int ret;
+
+	if (atomic_read(&dev->user->inotify_watches) >= max_user_watches)
+		return ERR_PTR(-ENOSPC);
+
+	watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL);
+	if (unlikely(!watch))
+		return ERR_PTR(-ENOMEM);
+
+	ret = inotify_dev_get_wd(dev, watch);
+	if (unlikely(ret)) {
+		kmem_cache_free(watch_cachep, watch);
+		return ERR_PTR(ret);
+	}
+
+	watch->mask = mask;
+	atomic_set(&watch->count, 0);
+	INIT_LIST_HEAD(&watch->d_list);
+	INIT_LIST_HEAD(&watch->i_list);
+
+	/* save a reference to device and bump the count to make it official */
+	get_inotify_dev(dev);
+	watch->dev = dev;
+
+	/*
+	 * Save a reference to the inode and bump the ref count to make it
+	 * official.  We hold a reference to nameidata, which makes this safe.
+	 */
+	watch->inode = igrab(inode);
+
+	/* bump our own count, corresponding to our entry in dev->watches */
+	get_inotify_watch(watch);
+
+	atomic_inc(&dev->user->inotify_watches);
+
+	return watch;
+}
+
+/*
+ * inotify_find_dev - find the watch associated with the given inode and dev
+ *
+ * Callers must hold inode->inotify_sem.
+ */
+static struct inotify_watch *inode_find_dev(struct inode *inode,
+					    struct inotify_device *dev)
+{
+	struct inotify_watch *watch;
+
+	list_for_each_entry(watch, &inode->inotify_watches, i_list) {
+		if (watch->dev == dev)
+			return watch;
+	}
+
+	return NULL;
+}
+
+/*
+ * remove_watch_no_event - remove_watch() without the IN_IGNORED event.
+ */
+static void remove_watch_no_event(struct inotify_watch *watch,
+				  struct inotify_device *dev)
+{
+	list_del(&watch->i_list);
+	list_del(&watch->d_list);
+
+	atomic_dec(&dev->user->inotify_watches);
+	idr_remove(&dev->idr, watch->wd);
+	put_inotify_watch(watch);
+}
+
+/*
+ * remove_watch - Remove a watch from both the device and the inode.  Sends
+ * the IN_IGNORED event to the given device signifying that the inode is no
+ * longer watched.
+ *
+ * Callers must hold both inode->inotify_sem and dev->sem.  We drop a
+ * reference to the inode before returning.
+ *
+ * The inode is not iput() so as to remain atomic.  If the inode needs to be
+ * iput(), the call returns one.  Otherwise, it returns zero.
+ */
+static void remove_watch(struct inotify_watch *watch,struct inotify_device *dev)
+{
+	inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL);
+	remove_watch_no_event(watch, dev);
+}
+
+/*
+ * inotify_inode_watched - returns nonzero if there are watches on this inode
+ * and zero otherwise.  We call this lockless, we do not care if we race.
+ */
+static inline int inotify_inode_watched(struct inode *inode)
+{
+	return !list_empty(&inode->inotify_watches);
+}
+
+/* Kernel API */
+
+/**
+ * inotify_inode_queue_event - queue an event to all watches on this inode
+ * @inode: inode event is originating from
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
+			       const char *name)
+{
+	struct inotify_watch *watch, *next;
+
+	if (!inotify_inode_watched(inode))
+		return;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		u32 watch_mask = watch->mask;
+		if (watch_mask & mask) {
+			struct inotify_device *dev = watch->dev;
+			get_inotify_watch(watch);
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, mask, cookie, name);
+			if (watch_mask & IN_ONESHOT)
+				remove_watch_no_event(watch, dev);
+			up(&dev->sem);
+			put_inotify_watch(watch);
+		}
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_queue_event);
+
+/**
+ * inotify_dentry_parent_queue_event - queue an event to a dentry's parent
+ * @dentry: the dentry in question, we queue against this dentry's parent
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask,
+				       u32 cookie, const char *name)
+{
+	struct dentry *parent;
+	struct inode *inode;
+
+	spin_lock(&dentry->d_lock);
+	parent = dentry->d_parent;
+	inode = parent->d_inode;
+
+	if (inotify_inode_watched(inode)) {
+		dget(parent);
+		spin_unlock(&dentry->d_lock);
+		inotify_inode_queue_event(inode, mask, cookie, name);
+		dput(parent);
+	} else
+		spin_unlock(&dentry->d_lock);
+}
+EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event);
+
+/**
+ * inotify_get_cookie - return a unique cookie for use in synchronizing events.
+ */
+u32 inotify_get_cookie(void)
+{
+	return atomic_inc_return(&inotify_cookie);
+}
+EXPORT_SYMBOL_GPL(inotify_get_cookie);
+
+/**
+ * inotify_unmount_inodes - an sb is unmounting.  handle any watched inodes.
+ * @list: list of inodes being unmounted (sb->s_inodes)
+ *
+ * Called with inode_lock held, protecting the unmounting super block's list
+ * of inodes, and with iprune_sem held, keeping shrink_icache_memory() at bay.
+ * We temporarily drop inode_lock, however, and CAN block.
+ */
+void inotify_unmount_inodes(struct list_head *list)
+{
+	struct inode *inode, *next_i;
+
+	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+		struct inotify_watch *watch, *next_w;
+		struct list_head *watches;
+
+		/*
+		 * We cannot __iget() an inode in state I_CLEAR, which is fine
+		 * as by that point the inode cannot have any watches.
+		 */
+		if (inode->i_state & I_CLEAR)
+			continue;
+
+		/* In case the remove_watch() drops a reference */
+		__iget(inode);
+
+		/*
+		 * We can safely drop inode_lock here because the per-sb list
+		 * of inodes must not change during unmount and iprune_sem
+		 * keeps shrink_icache_memory() away.
+		 */
+		spin_unlock(&inode_lock);
+
+		/* for each watch, send IN_UNMOUNT and then remove it */
+		down(&inode->inotify_sem);
+		watches = &inode->inotify_watches;
+		list_for_each_entry_safe(watch, next_w, watches, i_list) {
+			struct inotify_device *dev = watch->dev;
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, IN_UNMOUNT,0,NULL);
+			remove_watch(watch, dev);
+			up(&dev->sem);
+		}
+		up(&inode->inotify_sem);
+		iput(inode);		
+
+		spin_lock(&inode_lock);
+	}
+}
+EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
+
+/**
+ * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
+ * @inode: inode that is about to be removed
+ */
+void inotify_inode_is_dead(struct inode *inode)
+{
+	struct inotify_watch *watch, *next;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		struct inotify_device *dev = watch->dev;
+		down(&dev->sem);
+		remove_watch(watch, dev);
+		up(&dev->sem);
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_is_dead);
+
+/* Device Interface */
+
+static unsigned int inotify_poll(struct file *file, poll_table *wait)
+{
+	struct inotify_device *dev = file->private_data;
+	int ret = 0;
+
+	poll_wait(file, &dev->wq, wait);
+	down(&dev->sem);
+	if (!list_empty(&dev->events))
+		ret = POLLIN | POLLRDNORM;
+	up(&dev->sem);
+
+	return ret;
+}
+
+static ssize_t inotify_read(struct file *file, char __user *buf,
+			    size_t count, loff_t *pos)
+{
+	size_t event_size = sizeof (struct inotify_event);
+	struct inotify_device *dev;
+	char __user *start;
+	int ret;
+	DEFINE_WAIT(wait);
+
+	start = buf;
+	dev = file->private_data;
+
+	while (1) {
+		int events;
+
+		prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
+
+		down(&dev->sem);
+		events = !list_empty(&dev->events);
+		up(&dev->sem);
+		if (events) {
+			ret = 0;
+			break;
+		}
+
+		if (file->f_flags & O_NONBLOCK) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			ret = -EINTR;
+			break;
+		}
+
+		schedule();
+	}
+
+	finish_wait(&dev->wq, &wait);
+	if (ret)
+		return ret;
+
+	down(&dev->sem);
+	while (1) {
+		struct inotify_kernel_event *kevent;
+
+		ret = buf - start;
+		if (list_empty(&dev->events))
+			break;
+
+		kevent = inotify_dev_get_event(dev);
+		if (event_size + kevent->event.len > count)
+			break;
+
+		if (copy_to_user(buf, &kevent->event, event_size)) {
+			ret = -EFAULT;
+			break;
+		}
+		buf += event_size;
+		count -= event_size;
+
+		if (kevent->name) {
+			if (copy_to_user(buf, kevent->name, kevent->event.len)){
+				ret = -EFAULT;
+				break;
+			}
+			buf += kevent->event.len;
+			count -= kevent->event.len;
+		}
+
+		remove_kevent(dev, kevent);
+	}
+	up(&dev->sem);
+
+	return ret;
+}
+
+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 (unlikely(atomic_read(&user->inotify_devs) >= max_user_devices)) {
+		ret = -EMFILE;
+		goto out_err;
+	}
+
+	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	if (unlikely(!dev)) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	idr_init(&dev->idr);
+	INIT_LIST_HEAD(&dev->events);
+	INIT_LIST_HEAD(&dev->watches);
+	init_waitqueue_head(&dev->wq);
+	sema_init(&dev->sem, 1);
+	dev->event_count = 0;
+	dev->queue_size = 0;
+	dev->max_events = max_queued_events;
+	dev->user = user;
+	atomic_set(&dev->count, 0);
+
+	get_inotify_dev(dev);
+	atomic_inc(&user->inotify_devs);
+
+	file->private_data = dev;
+
+	ret = nonseekable_open(inode, file);
+	if (ret)
+		goto out_err;
+
+	return 0;
+out_err:
+	free_uid(user);
+	return ret;
+}
+
+static int inotify_release(struct inode *ignored, struct file *file)
+{
+	struct inotify_device *dev = file->private_data;
+
+	/*
+	 * Destroy all of the watches on this device.  Unfortunately, not very
+	 * pretty.  We cannot do a simple iteration over the list, because we
+	 * do not know the inode until we iterate to the watch.  But we need to
+	 * hold inode->inotify_sem before dev->sem.  The following works.
+	 */
+	while (1) {
+		struct inotify_watch *watch;
+		struct list_head *watches;
+		struct inode *inode;
+
+		down(&dev->sem);
+		watches = &dev->watches;
+		if (list_empty(watches)) {
+			up(&dev->sem);
+			break;
+		}
+		watch = list_entry(watches->next, struct inotify_watch, d_list);
+		get_inotify_watch(watch);
+		up(&dev->sem);
+
+		inode = watch->inode;
+		down(&inode->inotify_sem);
+		down(&dev->sem);
+		remove_watch_no_event(watch, dev);
+		up(&dev->sem);
+		up(&inode->inotify_sem);
+		put_inotify_watch(watch);
+	}
+
+	/* destroy all of the events on this device */
+	down(&dev->sem);
+	while (!list_empty(&dev->events))
+		inotify_dev_event_dequeue(dev);
+	up(&dev->sem);
+
+	/* free this device: the put matching the get in inotify_open() */
+	put_inotify_dev(dev);
+
+	return 0;
+}
+
+static int inotify_add_watch(struct inotify_device *dev, int fd, u32 mask)
+{
+	struct inotify_watch *watch, *old;
+	struct inode *inode;
+	struct file *filp;
+	int ret;
+
+	filp = fget(fd);
+	if (!filp)
+		return -EBADF;
+	inode = filp->f_dentry->d_inode;
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* don't let user-space set invalid bits: we don't want flags set */
+	mask &= IN_ALL_EVENTS;
+	if (!mask) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Handle the case of re-adding a watch on an (inode,dev) pair that we
+	 * are already watching.  We just update the mask and return its wd.
+	 */
+	old = inode_find_dev(inode, dev);
+	if (unlikely(old)) {
+		old->mask = mask;
+		ret = old->wd;
+		goto out;
+	}
+
+	watch = create_watch(dev, mask, inode);
+	if (unlikely(IS_ERR(watch))) {
+		ret = PTR_ERR(watch);
+		goto out;
+	}
+
+	/* Add the watch to the device's and the inode's list */
+	list_add(&watch->d_list, &dev->watches);
+	list_add(&watch->i_list, &inode->inotify_watches);
+	ret = watch->wd;
+
+out:
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	fput(filp);
+
+	return ret;
+}
+
+/*
+ * inotify_ignore - handle the INOTIFY_IGNORE ioctl, asking that a given wd be
+ * removed from the device.
+ *
+ * Can sleep.
+ */
+static int inotify_ignore(struct inotify_device *dev, s32 wd)
+{
+	struct inotify_watch *watch;
+	struct inode *inode;
+
+	down(&dev->sem);
+	watch = idr_find(&dev->idr, wd);
+	if (unlikely(!watch)) {
+		up(&dev->sem);
+		return -EINVAL;
+	}
+	get_inotify_watch(watch);
+	inode = watch->inode;
+	up(&dev->sem);
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* make sure that we did not race */
+	watch = idr_find(&dev->idr, wd);
+	if (likely(watch))
+		remove_watch(watch, dev);
+
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	put_inotify_watch(watch);
+
+	return 0;
+}
+
+static long inotify_ioctl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	struct inotify_device *dev;
+	struct inotify_watch_request request;
+	void __user *p;
+	int ret = -ENOTTY;
+	s32 wd;
+
+	dev = file->private_data;
+	p = (void __user *) arg;
+
+	switch (cmd) {
+	case INOTIFY_WATCH:
+		if (unlikely(copy_from_user(&request, p, sizeof (request)))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_add_watch(dev, request.fd, request.mask);
+		break;
+	case INOTIFY_IGNORE:
+		if (unlikely(get_user(wd, (int __user *) p))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_ignore(dev, wd);
+		break;
+	case FIONREAD:
+		ret = put_user(dev->queue_size, (int __user *) p);
+		break;
+	}
+
+	return ret;
+}
+
+static struct file_operations inotify_fops = {
+	.owner		= THIS_MODULE,
+	.poll		= inotify_poll,
+	.read		= inotify_read,
+	.open		= inotify_open,
+	.release	= inotify_release,
+	.unlocked_ioctl	= inotify_ioctl,
+	.compat_ioctl	= inotify_ioctl,
+};
+
+static struct miscdevice inotify_device = {
+	.minor  = MISC_DYNAMIC_MINOR,
+	.name	= "inotify",
+	.fops	= &inotify_fops,
+};
+
+/*
+ * inotify_init - Our initialization function.  Note that we cannnot return
+ * error because we have compiled-in VFS hooks.  So an (unlikely) failure here
+ * must result in panic().
+ */
+static int __init inotify_init(void)
+{
+	struct class_device *class;
+	int ret;
+
+	ret = misc_register(&inotify_device);
+	if (unlikely(ret))
+		panic("inotify: misc_register returned %d\n", ret);
+
+	max_queued_events = 8192;
+	max_user_devices = 128;
+	max_user_watches = 8192;
+
+	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);
+
+	printk(KERN_INFO "inotify device minor=%d\n", inotify_device.minor);
+
+	return 0;
+}
+
+module_init(inotify_init);
diff -urN linux-2.6.12-rc6/fs/Kconfig linux/fs/Kconfig
--- linux-2.6.12-rc6/fs/Kconfig	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/Kconfig	2005-06-13 11:28:04.000000000 -0400
@@ -339,6 +339,19 @@
 	  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 -urN linux-2.6.12-rc6/fs/Makefile linux/fs/Makefile
--- linux-2.6.12-rc6/fs/Makefile	2005-03-02 02:38:10.000000000 -0500
+++ linux/fs/Makefile	2005-06-13 11:28:04.000000000 -0400
@@ -11,6 +11,7 @@
 		attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \
 
+obj-$(CONFIG_INOTIFY)		+= inotify.o
 obj-$(CONFIG_EPOLL)		+= eventpoll.o
 obj-$(CONFIG_COMPAT)		+= compat.o
 
diff -urN linux-2.6.12-rc6/fs/namei.c linux/fs/namei.c
--- linux-2.6.12-rc6/fs/namei.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/namei.c	2005-06-13 11:28:04.000000000 -0400
@@ -21,7 +21,7 @@
 #include <linux/namei.h>
 #include <linux/quotaops.h>
 #include <linux/pagemap.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
 #include <linux/security.h>
@@ -1296,7 +1296,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;
@@ -1602,7 +1602,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;
@@ -1675,7 +1675,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;
@@ -1766,7 +1766,7 @@
 	}
 	up(&dentry->d_inode->i_sem);
 	if (!error) {
-		inode_dir_notify(dir, DN_DELETE);
+		fsnotify_rmdir(dentry, dentry->d_inode, dir);
 		d_delete(dentry);
 	}
 	dput(dentry);
@@ -1839,9 +1839,10 @@
 
 	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
 	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
+		fsnotify_unlink(dentry, dir);
 		d_delete(dentry);
-		inode_dir_notify(dir, DN_DELETE);
 	}
+
 	return error;
 }
 
@@ -1915,7 +1916,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;
@@ -1988,7 +1989,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;
@@ -2152,6 +2153,7 @@
 {
 	int error;
 	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	const char *old_name;
 
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
@@ -2173,18 +2175,18 @@
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
 
+	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
+
 	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, is_dir);
 	}
+	fsnotify_oldname_free(old_name);
+
 	return error;
 }
 
diff -urN linux-2.6.12-rc6/fs/nfsd/vfs.c linux/fs/nfsd/vfs.c
--- linux-2.6.12-rc6/fs/nfsd/vfs.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/nfsd/vfs.c	2005-06-13 11:28:04.000000000 -0400
@@ -45,7 +45,7 @@
 #endif /* CONFIG_NFSD_V3 */
 #include <linux/nfsd/nfsfh.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #ifdef CONFIG_NFSD_V4
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
@@ -861,7 +861,7 @@
 		nfsdstats.io_read += err;
 		*count = err;
 		err = 0;
-		dnotify_parent(file->f_dentry, DN_ACCESS);
+		fsnotify_access(file->f_dentry);
 	} else 
 		err = nfserrno(err);
 out:
@@ -917,7 +917,7 @@
 	set_fs(oldfs);
 	if (err >= 0) {
 		nfsdstats.io_write += cnt;
-		dnotify_parent(file->f_dentry, DN_MODIFY);
+		fsnotify_modify(file->f_dentry);
 	}
 
 	/* clear setuid/setgid flag after write */
diff -urN linux-2.6.12-rc6/fs/open.c linux/fs/open.c
--- linux-2.6.12-rc6/fs/open.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/open.c	2005-06-13 11:28:04.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/smp_lock.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
@@ -944,9 +944,11 @@
 		fd = get_unused_fd();
 		if (fd >= 0) {
 			struct file *f = filp_open(tmp, flags, mode);
+
 			error = PTR_ERR(f);
 			if (IS_ERR(f))
 				goto out_error;
+			fsnotify_open(f->f_dentry);
 			fd_install(fd, f);
 		}
 out:
diff -urN linux-2.6.12-rc6/fs/read_write.c linux/fs/read_write.c
--- linux-2.6.12-rc6/fs/read_write.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/read_write.c	2005-06-13 11:28:04.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/security.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
@@ -239,7 +239,7 @@
 			else
 				ret = do_sync_read(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_ACCESS);
+				fsnotify_access(file->f_dentry);
 				current->rchar += ret;
 			}
 			current->syscr++;
@@ -287,7 +287,7 @@
 			else
 				ret = do_sync_write(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_MODIFY);
+				fsnotify_modify(file->f_dentry);
 				current->wchar += ret;
 			}
 			current->syscw++;
@@ -523,9 +523,12 @@
 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) {
+		if (type == READ)
+			fsnotify_access(file->f_dentry);
+		else
+			fsnotify_modify(file->f_dentry);
+	}
 	return ret;
 Efault:
 	ret = -EFAULT;
diff -urN linux-2.6.12-rc6/fs/sysfs/file.c linux/fs/sysfs/file.c
--- linux-2.6.12-rc6/fs/sysfs/file.c	2005-06-07 15:47:52.000000000 -0400
+++ linux/fs/sysfs/file.c	2005-06-13 11:28:04.000000000 -0400
@@ -3,7 +3,7 @@
  */
 
 #include <linux/module.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/kobject.h>
 #include <asm/uaccess.h>
 #include <asm/semaphore.h>
@@ -389,9 +389,6 @@
  * sysfs_update_file - update the modified timestamp on an object attribute.
  * @kobj: object we're acting for.
  * @attr: attribute descriptor.
- *
- * Also call dnotify for the dentry, which lots of userspace programs
- * use.
  */
 int sysfs_update_file(struct kobject * kobj, const struct attribute * attr)
 {
@@ -406,7 +403,7 @@
 		if (victim->d_inode && 
 		    (victim->d_parent->d_inode == dir->d_inode)) {
 			victim->d_inode->i_mtime = CURRENT_TIME;
-			dnotify_parent(victim, DN_MODIFY);
+			fsnotify_modify(victim);
 
 			/**
 			 * Drop reference from initial sysfs_get_dentry().
diff -urN linux-2.6.12-rc6/fs/xattr.c linux/fs/xattr.c
--- linux-2.6.12-rc6/fs/xattr.c	2005-03-02 02:38:07.000000000 -0500
+++ linux/fs/xattr.c	2005-06-13 11:28:04.000000000 -0400
@@ -16,6 +16,7 @@
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/module.h>
+#include <linux/fsnotify.h>
 #include <asm/uaccess.h>
 
 /*
@@ -57,8 +58,10 @@
 		if (error)
 			goto out;
 		error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
-		if (!error)
+		if (!error) {
+			fsnotify_xattr(d);
 			security_inode_post_setxattr(d, kname, kvalue, size, flags);
+		}
 out:
 		up(&d->d_inode->i_sem);
 	}
diff -urN linux-2.6.12-rc6/include/linux/fs.h linux/include/linux/fs.h
--- linux-2.6.12-rc6/include/linux/fs.h	2005-06-07 15:47:53.000000000 -0400
+++ linux/include/linux/fs.h	2005-06-17 11:01:37.000000000 -0400
@@ -471,6 +471,11 @@
 	struct dnotify_struct	*i_dnotify; /* for directory notifications */
 #endif
 
+#ifdef CONFIG_INOTIFY
+	struct list_head	inotify_watches; /* watches on this inode */
+	struct semaphore	inotify_sem;	/* protects the watches list */
+#endif
+
 	unsigned long		i_state;
 	unsigned long		dirtied_when;	/* jiffies of first dirtying */
 
@@ -1369,7 +1374,6 @@
 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 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.12-rc6/include/linux/fsnotify.h linux/include/linux/fsnotify.h
--- linux-2.6.12-rc6/include/linux/fsnotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/fsnotify.h	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,257 @@
+#ifndef _LINUX_FS_NOTIFY_H
+#define _LINUX_FS_NOTIFY_H
+
+/*
+ * include/linux/fsnotify.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 <linux/dnotify.h>
+#include <linux/inotify.h>
+
+/*
+ * 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,
+				 int isdir)
+{
+	u32 cookie = inotify_get_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);
+	}
+
+	if (isdir)
+		isdir = IN_ISDIR;
+	inotify_inode_queue_event(old_dir, IN_MOVED_FROM|isdir,cookie,old_name);
+	inotify_inode_queue_event(new_dir, IN_MOVED_TO|isdir, cookie, new_name);
+}
+
+/*
+ * fsnotify_unlink - file was unlinked
+ */
+static inline void fsnotify_unlink(struct dentry *dentry, struct inode *dir)
+{
+	struct inode *inode = dentry->d_inode;
+
+	inode_dir_notify(dir, DN_DELETE);
+	inotify_inode_queue_event(dir, IN_DELETE, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL);
+
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * 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|IN_ISDIR,0,dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF | IN_ISDIR, 0, NULL);
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * fsnotify_create - 'name' was linked in
+ */
+static inline void fsnotify_create(struct inode *inode, const char *name)
+{
+	inode_dir_notify(inode, DN_CREATE);
+	inotify_inode_queue_event(inode, IN_CREATE, 0, name);
+}
+
+/*
+ * 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 | IN_ISDIR, 0, name);
+}
+
+/*
+ * fsnotify_access - file was read
+ */
+static inline void fsnotify_access(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ACCESS;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_ACCESS);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_modify - file was modified
+ */
+static inline void fsnotify_modify(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_MODIFY;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_MODIFY);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_open - file was opened
+ */
+static inline void fsnotify_open(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_OPEN;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+}
+
+/*
+ * fsnotify_close - file was closed
+ */
+static inline void fsnotify_close(struct file *file)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+	mode_t mode = file->f_mode;
+	u32 mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_xattr - extended attributes were changed
+ */
+static inline void fsnotify_xattr(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ATTRIB;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	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)
+{
+	struct inode *inode = dentry->d_inode;
+	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) {
+		if (S_ISDIR(inode->i_mode))
+			in_mask |= IN_ISDIR;
+		inotify_inode_queue_event(inode, in_mask, 0, NULL);
+		inotify_dentry_parent_queue_event(dentry, in_mask, 0,
+						  dentry->d_name.name);
+	}
+}
+
+#ifdef CONFIG_INOTIFY	/* inotify helpers */
+
+/*
+ * fsnotify_oldname_init - save off the old filename before we change it
+ *
+ * XXX: This could be kstrdup if only we could add that to lib/string.c
+ */
+static inline const char *fsnotify_oldname_init(const char *name)
+{
+	size_t len;
+	char *buf;
+
+	len = strlen(name) + 1;
+	buf = kmalloc(len, GFP_KERNEL);
+	if (likely(buf))
+		memcpy(buf, name, len);
+	return buf;
+}
+
+/*
+ * 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 const char *fsnotify_oldname_init(const char *name)
+{
+	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.12-rc6/include/linux/inotify.h linux/include/linux/inotify.h
--- linux-2.6.12-rc6/include/linux/inotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/inotify.h	2005-06-13 11:28:04.000000000 -0400
@@ -0,0 +1,124 @@
+/*
+ * Inode based directory notification for Linux
+ *
+ * Copyright (C) 2005 John McCutchan
+ */
+
+#ifndef _LINUX_INOTIFY_H
+#define _LINUX_INOTIFY_H
+
+#include <linux/types.h>
+
+/*
+ * 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 to synchronize two events */
+	__u32		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 {
+	int		fd;		/* fd of filename to watch */
+	__u32		mask;		/* event mask */
+};
+
+/* the following are legal, implemented events that user-space can watch for */
+#define IN_ACCESS		0x00000001	/* File was accessed */
+#define IN_MODIFY		0x00000002	/* File was modified */
+#define IN_ATTRIB		0x00000004	/* Metadata changed */
+#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_CREATE		0x00000100	/* Subfile was created */
+#define IN_DELETE		0x00000200	/* Subfile was deleted */
+#define IN_DELETE_SELF		0x00000400	/* Self was deleted */
+
+/* the following are legal events.  they are sent as needed to any watch */
+#define IN_UNMOUNT		0x00002000	/* Backing fs was unmounted */
+#define IN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
+#define IN_IGNORED		0x00008000	/* File was ignored */
+
+/* helper events */
+#define IN_CLOSE		(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */
+#define IN_MOVE			(IN_MOVED_FROM | IN_MOVED_TO) /* moves */
+
+/* special flags */
+#define IN_ISDIR		0x40000000	/* event occurred against dir */
+#define IN_ONESHOT		0x80000000	/* only send event once */
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define IN_ALL_EVENTS	(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
+			 IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \
+			 IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF)
+
+#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 <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/config.h>
+
+#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_unmount_inodes(struct list_head *);
+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_unmount_inodes(struct list_head *list)
+{
+}
+
+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.12-rc6/include/linux/sched.h linux/include/linux/sched.h
--- linux-2.6.12-rc6/include/linux/sched.h	2005-06-07 15:47:53.000000000 -0400
+++ linux/include/linux/sched.h	2005-06-13 11:28:04.000000000 -0400
@@ -404,6 +404,10 @@
 	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? */
+#ifdef CONFIG_INOTIFY
+	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? */
+#endif
 	/* 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.12-rc6/kernel/user.c linux/kernel/user.c
--- linux-2.6.12-rc6/kernel/user.c	2005-06-07 15:47:53.000000000 -0400
+++ linux/kernel/user.c	2005-06-13 11:28:04.000000000 -0400
@@ -120,6 +120,10 @@
 		atomic_set(&new->processes, 0);
 		atomic_set(&new->files, 0);
 		atomic_set(&new->sigpending, 0);
+#ifdef CONFIG_INOTIFY
+		atomic_set(&new->inotify_watches, 0);
+		atomic_set(&new->inotify_devs, 0);
+#endif
 
 		new->mq_bytes = 0;
 		new->locked_shm = 0;

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 15:15         ` [patch] inotify, improved Robert Love
@ 2005-06-17 15:37           ` Chris Friesen
  2005-06-17 15:44             ` Robert Love
  2005-06-17 17:20           ` Zach Brown
  1 sibling, 1 reply; 45+ messages in thread
From: Chris Friesen @ 2005-06-17 15:37 UTC (permalink / raw)
  To: Robert Love
  Cc: Nick Piggin, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

Speaking of inotify improvements...

On a newsgroup someone was using inotify, but was asking if there was 
any way to also determine which process/user had caused the notification.

Is this something that would make sense (as an optional bit of 
information) in inotify?

Chris

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 15:37           ` Chris Friesen
@ 2005-06-17 15:44             ` Robert Love
  2005-06-17 16:11               ` Valdis.Kletnieks
  2005-06-17 16:40               ` Chris Friesen
  0 siblings, 2 replies; 45+ messages in thread
From: Robert Love @ 2005-06-17 15:44 UTC (permalink / raw)
  To: Chris Friesen
  Cc: Nick Piggin, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, 2005-06-17 at 09:37 -0600, Chris Friesen wrote:

Hi, Chris.

> On a newsgroup someone was using inotify, but was asking if there was 
> any way to also determine which process/user had caused the notification.
> 
> Is this something that would make sense (as an optional bit of 
> information) in inotify?

It is definitely something that could be added, technically speaking.

I have been hesitant, though.  I do not want feature creep to be a
deterrent to acceptance into the Linux kernel.  I also think that there
could be arguments about security.  Sending the event is one thing,
telling which pid (and thus what user, etc.) caused the event is
another.  For example, we can make the argument that read rights on a
file are tantamount to the right to receive a read event.  But can we
say that read rights are enough for a unprivileged user to know that
root at pid 820 is writing the file?  I don't know.

I'd add it if there were consensus.  I don't know that it makes sense,
though.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 15:44             ` Robert Love
@ 2005-06-17 16:11               ` Valdis.Kletnieks
  2005-06-17 16:29                 ` Robert Love
  2005-06-17 16:36                 ` Chris Friesen
  2005-06-17 16:40               ` Chris Friesen
  1 sibling, 2 replies; 45+ messages in thread
From: Valdis.Kletnieks @ 2005-06-17 16:11 UTC (permalink / raw)
  To: Robert Love
  Cc: Chris Friesen, Nick Piggin, Zach Brown, linux-kernel, Al Viro,
	John McCutchan, Andrew Morton

[-- Attachment #1: Type: text/plain, Size: 722 bytes --]

On Fri, 17 Jun 2005 11:44:38 EDT, Robert Love said:
> I have been hesitant, though.  I do not want feature creep to be a
> deterrent to acceptance into the Linux kernel.  I also think that there
> could be arguments about security.  Sending the event is one thing,
> telling which pid (and thus what user, etc.) caused the event is
> another.  For example, we can make the argument that read rights on a
> file are tantamount to the right to receive a read event.  But can we
> say that read rights are enough for a unprivileged user to know that
> root at pid 820 is writing the file?  I don't know.

It's also racy as hell.  By the time the inotify gets delivered to the
userspace process, pid 820 may be long gone.....

[-- Attachment #2: Type: application/pgp-signature, Size: 226 bytes --]

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 16:11               ` Valdis.Kletnieks
@ 2005-06-17 16:29                 ` Robert Love
  2005-06-17 16:36                 ` Chris Friesen
  1 sibling, 0 replies; 45+ messages in thread
From: Robert Love @ 2005-06-17 16:29 UTC (permalink / raw)
  To: Valdis.Kletnieks
  Cc: Chris Friesen, Nick Piggin, Zach Brown, linux-kernel, Al Viro,
	John McCutchan, Andrew Morton

On Fri, 2005-06-17 at 12:11 -0400, Valdis.Kletnieks@vt.edu wrote:

> It's also racy as hell.  By the time the inotify gets delivered to the
> userspace process, pid 820 may be long gone.....

Yah, no one would expect otherwise.  One nice feature of it would be
able to see if the pid is equal to a known pid, say yourself or some
worker process.

But I've also received requests to send the size and offset of a
read/write, the user, etc. etc.  I just say no.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 16:11               ` Valdis.Kletnieks
  2005-06-17 16:29                 ` Robert Love
@ 2005-06-17 16:36                 ` Chris Friesen
  2005-06-17 16:43                   ` Chris Wright
  2005-06-17 16:46                   ` Muli Ben-Yehuda
  1 sibling, 2 replies; 45+ messages in thread
From: Chris Friesen @ 2005-06-17 16:36 UTC (permalink / raw)
  To: Valdis.Kletnieks
  Cc: Robert Love, Nick Piggin, Zach Brown, linux-kernel, Al Viro,
	John McCutchan, Andrew Morton

Valdis.Kletnieks@vt.edu wrote:

> It's also racy as hell.  By the time the inotify gets delivered to the
> userspace process, pid 820 may be long gone.....

Yep.  But I can see uses for people to want to log all activity on 
specific directory trees.  Think audit trails, etc.

Imagine root getting a log like:

Date: Jan 1,2006: file /foo/evidence.txt changed by user blah, pid 
<666>, commandline: "vi evidence.txt"

Chris

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 15:44             ` Robert Love
  2005-06-17 16:11               ` Valdis.Kletnieks
@ 2005-06-17 16:40               ` Chris Friesen
  2005-06-17 17:57                 ` John McCutchan
  1 sibling, 1 reply; 45+ messages in thread
From: Chris Friesen @ 2005-06-17 16:40 UTC (permalink / raw)
  To: Robert Love
  Cc: Nick Piggin, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

Robert Love wrote:
> On Fri, 2005-06-17 at 09:37 -0600, Chris Friesen wrote:

>>On a newsgroup someone was using inotify, but was asking if there was 
>>any way to also determine which process/user had caused the notification.

> I have been hesitant, though.  I do not want feature creep to be a
> deterrent to acceptance into the Linux kernel.

Absolutely.

> I also think that there could be arguments about security.
> ...can we
> say that read rights are enough for a unprivileged user to know that
> root at pid 820 is writing the file?  I don't know.

I'm sure some reasonable rules could be determined.  Maybe you'd need to 
be the owner of the file to get the extra info, with root able to 
monitor everything.

Maybe there should be a way to load plugins into inotify (something like 
netfilter) so that people load modules to send themselves whatever 
information they want...

Let's get it in first though before thinking too hard about extra stuff.

Chris


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 16:36                 ` Chris Friesen
@ 2005-06-17 16:43                   ` Chris Wright
  2005-06-17 16:46                   ` Muli Ben-Yehuda
  1 sibling, 0 replies; 45+ messages in thread
From: Chris Wright @ 2005-06-17 16:43 UTC (permalink / raw)
  To: Chris Friesen
  Cc: Valdis.Kletnieks, Robert Love, Nick Piggin, Zach Brown,
	linux-kernel, Al Viro, John McCutchan, Andrew Morton

* Chris Friesen (cfriesen@nortel.com) wrote:
> Valdis.Kletnieks@vt.edu wrote:
> 
> >It's also racy as hell.  By the time the inotify gets delivered to the
> >userspace process, pid 820 may be long gone.....
> 
> Yep.  But I can see uses for people to want to log all activity on 
> specific directory trees.  Think audit trails, etc.

This is already done by the audit patch.  I don't think inotify has
quite enough info to build a comprehensive audit record.

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 16:36                 ` Chris Friesen
  2005-06-17 16:43                   ` Chris Wright
@ 2005-06-17 16:46                   ` Muli Ben-Yehuda
  1 sibling, 0 replies; 45+ messages in thread
From: Muli Ben-Yehuda @ 2005-06-17 16:46 UTC (permalink / raw)
  To: Chris Friesen
  Cc: Valdis.Kletnieks, Robert Love, Nick Piggin, Zach Brown,
	linux-kernel, Al Viro, John McCutchan, Andrew Morton

On Fri, Jun 17, 2005 at 10:36:03AM -0600, Chris Friesen wrote:
> Valdis.Kletnieks@vt.edu wrote:
> 
> >It's also racy as hell.  By the time the inotify gets delivered to the
> >userspace process, pid 820 may be long gone.....
> 
> Yep.  But I can see uses for people to want to log all activity on 
> specific directory trees.  Think audit trails, etc.

If it's an audit trail, it should be provided by the audit subsystem,
not just inotify.

Cheers,
Muli
-- 
Muli Ben-Yehuda
http://www.mulix.org | http://mulix.livejournal.com/


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-16 18:25   ` Robert Love
  2005-06-17  1:30     ` Nick Piggin
@ 2005-06-17 17:07     ` Arnd Bergmann
  2005-06-17 17:54       ` Christoph Hellwig
                         ` (2 more replies)
  1 sibling, 3 replies; 45+ messages in thread
From: Arnd Bergmann @ 2005-06-17 17:07 UTC (permalink / raw)
  To: Robert Love
  Cc: Zach Brown, linux-kernel, Al Viro, John McCutchan, Andrew Morton

On Dunnersdag 16 Juni 2005 20:25, Robert Love wrote:
> +Q: Why a device node?
> +
> +A: The second biggest problem with dnotify is that the user
> +interface sucks ass.  Signals are a terrible, terrible interface
> +for file notification.  Or for anything, for that matter.  The
> +idea solution, from all perspectives, is a file descriptor based
> +one that allows basic file I/O and poll/select.  Obtaining the
> +fd and managing the watches could of been done either via a
> +device file or a family of new system calls.  We decided to
> +implement a device file because adding three or four new system
> +calls that mirrored open, close, and ioctl seemed silly.  A
> +character device makes sense from user-space and was easy to
> +implement inside of the kernel.

Sorry to bring up a topic that should have been settled a long time ago.

I found that the interface consisting of
 - open a handle
 - add a file descriptor with an event mask to handle
 - remove a file/watch descriptor from handle
 - wait on handle, get events
 - close handle

in inotify is _very_ similar to how epoll is represented to user
space. Is there a good reason that epoll is a set of syscalls while
inotify is a character device, or is one of them simply wrong?

	Arnd <><

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 15:15         ` [patch] inotify, improved Robert Love
  2005-06-17 15:37           ` Chris Friesen
@ 2005-06-17 17:20           ` Zach Brown
  2005-06-17 17:54             ` John McCutchan
  1 sibling, 1 reply; 45+ messages in thread
From: Zach Brown @ 2005-06-17 17:20 UTC (permalink / raw)
  To: Robert Love; +Cc: Nick Piggin, linux-kernel, John McCutchan

[-- Attachment #1: Type: text/plain, Size: 646 bytes --]


> +		schedule();

Here's a stab at getting rid of that raw schedule() in inotify_read().
It maintains the behaviour where it returns when an event doesn't fit
and returns after events have been copied instead of sleeping.  It
changes behaviour in that it returns partial reads that suceeded instead
of the error that stopped processing.  It also lets threads who race out
of a wakeup to find an empty list go back to sleep instead of returning
0.  Dunno if that's behaviour you'd prefer but it seemed reasonable.  I
hope that lockless list_empty() is OK, I didn't think very hard about it.

Compiles but totally untested.  Check my work :)

- z

[-- Attachment #2: inotify-use-w-e-i-0.patch --]
[-- Type: text/x-patch, Size: 1868 bytes --]

Index: 2.6-mm-inotify-throwaway/fs/inotify.c
===================================================================
--- 2.6-mm-inotify-throwaway.orig/fs/inotify.c	2005-06-17 09:32:52.000000000 -0700
+++ 2.6-mm-inotify-throwaway/fs/inotify.c	2005-06-17 10:16:11.000000000 -0700
@@ -639,52 +639,32 @@
 static ssize_t inotify_read(struct file *file, char __user *buf,
 			    size_t count, loff_t *pos)
 {
-	size_t event_size = sizeof (struct inotify_event);
-	struct inotify_device *dev;
-	char __user *start;
-	int ret;
-	DEFINE_WAIT(wait);
-
-	start = buf;
-	dev = file->private_data;
-
-	while (1) {
-		int events;
-
-		prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
-
-		down(&dev->sem);
-		events = !list_empty(&dev->events);
-		up(&dev->sem);
-		if (events) {
-			ret = 0;
-			break;
-		}
-
-		if (file->f_flags & O_NONBLOCK) {
-			ret = -EAGAIN;
-			break;
-		}
-
-		if (signal_pending(current)) {
-			ret = -EINTR;
-			break;
-		}
-
-		schedule();
-	}
-
-	finish_wait(&dev->wq, &wait);
-	if (ret)
-		return ret;
+	struct inotify_device *dev = file->private_data;
+	char __user *start = buf;
+	int ret = 0;
 
 	down(&dev->sem);
 	while (1) {
 		struct inotify_kernel_event *kevent;
+		static size_t event_size = sizeof (struct inotify_event);
 
-		ret = buf - start;
-		if (list_empty(&dev->events))
-			break;
+		if (list_empty(&dev->events)) {
+			/* return partial instead of sleeping */
+			if (buf > start)
+				break;
+			if (file->f_flags & O_NONBLOCK) {
+				ret = -EAGAIN;
+				break;
+			}
+			up(&dev->sem);
+			ret = wait_event_interruptible(dev->wq,
+						!list_empty(&dev->events));
+			down(&dev->sem);
+			if (ret)
+				break;
+			continue;
+
+		}
 
 		kevent = inotify_dev_get_event(dev);
 		if (event_size + kevent->event.len > count)
@@ -710,6 +690,9 @@
 	}
 	up(&dev->sem);
 
+	if (buf > start)
+		ret = buf - start;
+
 	return ret;
 }
 

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 17:07     ` [patch] inotify Arnd Bergmann
@ 2005-06-17 17:54       ` Christoph Hellwig
  2005-06-17 18:12         ` John McCutchan
  2005-06-17 18:16         ` Robert Love
  2005-06-17 17:56       ` John McCutchan
  2005-06-18  1:51       ` Chris Wedgwood
  2 siblings, 2 replies; 45+ messages in thread
From: Christoph Hellwig @ 2005-06-17 17:54 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Robert Love, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, Jun 17, 2005 at 07:07:38PM +0200, Arnd Bergmann wrote:
> Sorry to bring up a topic that should have been settled a long time ago.
> 
> I found that the interface consisting of
>  - open a handle
>  - add a file descriptor with an event mask to handle
>  - remove a file/watch descriptor from handle
>  - wait on handle, get events
>  - close handle
> 
> in inotify is _very_ similar to how epoll is represented to user
> space. Is there a good reason that epoll is a set of syscalls while
> inotify is a character device, or is one of them simply wrong?

It's because Robert and John insist on their horrible interface and
simply ignore any feedback on how to do a better one.

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 17:20           ` Zach Brown
@ 2005-06-17 17:54             ` John McCutchan
  2005-06-17 17:56               ` Zach Brown
  0 siblings, 1 reply; 45+ messages in thread
From: John McCutchan @ 2005-06-17 17:54 UTC (permalink / raw)
  To: Zach Brown; +Cc: Robert Love, Nick Piggin, linux-kernel

On Fri, Jun 17, 2005 at 10:20:20AM -0700, Zach Brown wrote:
> 
> > +		schedule();
> 
> Here's a stab at getting rid of that raw schedule() in inotify_read().
> It maintains the behaviour where it returns when an event doesn't fit
> and returns after events have been copied instead of sleeping.  It
> changes behaviour in that it returns partial reads that suceeded instead
> of the error that stopped processing.  It also lets threads who race out
> of a wakeup to find an empty list go back to sleep instead of returning
> 0.  Dunno if that's behaviour you'd prefer but it seemed reasonable.  I
> hope that lockless list_empty() is OK, I didn't think very hard about it.
> 

I really don't like sending partial events. I don't think it's worth the
extra effort in tracking how much of an event we sent out last time. I
also don't see any added benefit to user space when providing partial
events. It's going to complicate the user space event parsing code to.

> Compiles but totally untested.  Check my work :)

At first glance, I don't see any code to restart the partially sent
event. Am I missing the obvious or what? Without that, the user would
get the first half of an event, then get both halfs on the next read.
That simply won't work. 

John

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 17:07     ` [patch] inotify Arnd Bergmann
  2005-06-17 17:54       ` Christoph Hellwig
@ 2005-06-17 17:56       ` John McCutchan
  2005-06-17 21:33         ` Andrew Morton
  2005-06-18  1:51       ` Chris Wedgwood
  2 siblings, 1 reply; 45+ messages in thread
From: John McCutchan @ 2005-06-17 17:56 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Robert Love, Zach Brown, linux-kernel, Al Viro, Andrew Morton

On Fri, Jun 17, 2005 at 07:07:38PM +0200, Arnd Bergmann wrote:
> On Dunnersdag 16 Juni 2005 20:25, Robert Love wrote:
> > +Q: Why a device node?
> > +
> > +A: The second biggest problem with dnotify is that the user
> > +interface sucks ass. ?Signals are a terrible, terrible interface
> > +for file notification. ?Or for anything, for that matter. ?The
> > +idea solution, from all perspectives, is a file descriptor based
> > +one that allows basic file I/O and poll/select. ?Obtaining the
> > +fd and managing the watches could of been done either via a
> > +device file or a family of new system calls. ?We decided to
> > +implement a device file because adding three or four new system
> > +calls that mirrored open, close, and ioctl seemed silly. ?A
> > +character device makes sense from user-space and was easy to
> > +implement inside of the kernel.
> 
> Sorry to bring up a topic that should have been settled a long time ago.
> 

This was settled a long time ago. Robert, Andrew, and I had an off-list
discussion months ago, and we all agreed that this was the right
interface for inotify.

John

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 17:54             ` John McCutchan
@ 2005-06-17 17:56               ` Zach Brown
  2005-06-17 18:15                 ` John McCutchan
  0 siblings, 1 reply; 45+ messages in thread
From: Zach Brown @ 2005-06-17 17:56 UTC (permalink / raw)
  To: John McCutchan; +Cc: Robert Love, Nick Piggin, linux-kernel

John McCutchan wrote:

> I really don't like sending partial events. 

Partial reads, not partial events.  Just like the previous code, it
returns to userspace after copying the events it found in the list
before it went empty.  It doesn't go back to sleep to fill the rest of
the buffer like a more classical blocking read() method would.

Where it differs is in what happens if you get errors copying events
after having successfully copied some.  The previous code would return
the error, that patch would return the bytes used by the good events.

- z

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 16:40               ` Chris Friesen
@ 2005-06-17 17:57                 ` John McCutchan
  0 siblings, 0 replies; 45+ messages in thread
From: John McCutchan @ 2005-06-17 17:57 UTC (permalink / raw)
  To: Chris Friesen
  Cc: Robert Love, Nick Piggin, Zach Brown, linux-kernel, Al Viro,
	Andrew Morton

On Fri, Jun 17, 2005 at 10:40:32AM -0600, Chris Friesen wrote:
> Robert Love wrote:
> >On Fri, 2005-06-17 at 09:37 -0600, Chris Friesen wrote:
> 
> >>On a newsgroup someone was using inotify, but was asking if there was 
> >>any way to also determine which process/user had caused the notification.
> 
> >I have been hesitant, though.  I do not want feature creep to be a
> >deterrent to acceptance into the Linux kernel.
> 
> Absolutely.
> 
> >I also think that there could be arguments about security.
> >...can we
> >say that read rights are enough for a unprivileged user to know that
> >root at pid 820 is writing the file?  I don't know.
> 
> I'm sure some reasonable rules could be determined.  Maybe you'd need to 
> be the owner of the file to get the extra info, with root able to 
> monitor everything.
> 
> Maybe there should be a way to load plugins into inotify (something like 
> netfilter) so that people load modules to send themselves whatever 
> information they want...

This is probably a good idea for the _audit_ system. Inotify was
designed to do 1 task well, and it should stay that way.

John

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 17:54       ` Christoph Hellwig
@ 2005-06-17 18:12         ` John McCutchan
  2005-06-17 18:16         ` Robert Love
  1 sibling, 0 replies; 45+ messages in thread
From: John McCutchan @ 2005-06-17 18:12 UTC (permalink / raw)
  To: Christoph Hellwig, Arnd Bergmann, Robert Love, Zach Brown,
	linux-kernel, Al Viro, Andrew Morton

On Fri, Jun 17, 2005 at 06:54:04PM +0100, Christoph Hellwig wrote:
> On Fri, Jun 17, 2005 at 07:07:38PM +0200, Arnd Bergmann wrote:
> > Sorry to bring up a topic that should have been settled a long time ago.
> > 
> > I found that the interface consisting of
> >  - open a handle
> >  - add a file descriptor with an event mask to handle
> >  - remove a file/watch descriptor from handle
> >  - wait on handle, get events
> >  - close handle
> > 
> > in inotify is _very_ similar to how epoll is represented to user
> > space. Is there a good reason that epoll is a set of syscalls while
> > inotify is a character device, or is one of them simply wrong?
> 
> It's because Robert and John insist on their horrible interface and
> simply ignore any feedback on how to do a better one.

Your feedback was considered during the off list discussion, that you
were a part of. In the end Robert, Andrew, and I agreed on the current
interface.


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 17:56               ` Zach Brown
@ 2005-06-17 18:15                 ` John McCutchan
  2005-06-17 18:17                   ` Zach Brown
  0 siblings, 1 reply; 45+ messages in thread
From: John McCutchan @ 2005-06-17 18:15 UTC (permalink / raw)
  To: Zach Brown; +Cc: Robert Love, Nick Piggin, linux-kernel

On Fri, Jun 17, 2005 at 10:56:17AM -0700, Zach Brown wrote:
> John McCutchan wrote:
> 
> > I really don't like sending partial events. 
> 
> Partial reads, not partial events.  Just like the previous code, it
> returns to userspace after copying the events it found in the list
> before it went empty.  It doesn't go back to sleep to fill the rest of
> the buffer like a more classical blocking read() method would.
> 
> Where it differs is in what happens if you get errors copying events
> after having successfully copied some.  The previous code would return
> the error, that patch would return the bytes used by the good events.

My bad. Shouldn't we return the error code to user space though? We
shouldn't be hiding errors in the app. How does read() handle an error
part way through a read?

John

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 17:54       ` Christoph Hellwig
  2005-06-17 18:12         ` John McCutchan
@ 2005-06-17 18:16         ` Robert Love
  2005-06-17 18:28           ` Christoph Hellwig
  1 sibling, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-06-17 18:16 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Arnd Bergmann, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, 2005-06-17 at 18:54 +0100, Christoph Hellwig wrote:

> It's because Robert and John insist on their horrible interface and
> simply ignore any feedback on how to do a better one.

We considered your feedback, Christoph.  Ultimately, John, Andrew, and I
settled on the current approach.  In life, not everyone agrees on every
little detail and there usually exists a large difference between "not
exactly the same" and "horrible".  And never does the histrionics result
in anything productive.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify, improved.
  2005-06-17 18:15                 ` John McCutchan
@ 2005-06-17 18:17                   ` Zach Brown
  0 siblings, 0 replies; 45+ messages in thread
From: Zach Brown @ 2005-06-17 18:17 UTC (permalink / raw)
  To: John McCutchan; +Cc: Robert Love, Nick Piggin, linux-kernel


> My bad. Shouldn't we return the error code to user space though? We
> shouldn't be hiding errors in the app. How does read() handle an error
> part way through a read?

My preference is to give userspace a chance to work with what it can,
though I'm not sure what the N read paths do right now.

- z

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 18:16         ` Robert Love
@ 2005-06-17 18:28           ` Christoph Hellwig
  2005-06-17 18:38             ` Robert Love
  0 siblings, 1 reply; 45+ messages in thread
From: Christoph Hellwig @ 2005-06-17 18:28 UTC (permalink / raw)
  To: Robert Love
  Cc: Arnd Bergmann, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, Jun 17, 2005 at 02:16:53PM -0400, Robert Love wrote:
> We considered your feedback, Christoph.  Ultimately, John, Andrew, and I
> settled on the current approach.  In life, not everyone agrees on every
> little detail and there usually exists a large difference between "not
> exactly the same" and "horrible".  And never does the histrionics result
> in anything productive.

This shows exactly on how you're refusing feedback on the basis totally
unfounded claims again.

You are using ioctl as an really bad syscall multiplexer.  You're
not using the file descriptor it's called on at all, so it does not qualify
as a valid ioctl() usage even under the most lax rules.

Also you claimed the resource shortage for the proposed architecture
with just a single syscall, aka one watch per fd without showing any
reasons why that would be true, in fact by any means there's no reason
to believe file descriptors are a rare ressource in a modern Linux system.

I don't care whether you adopt my interface proposal or a different passable
one, but the current one is not acceptable at all.

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 18:28           ` Christoph Hellwig
@ 2005-06-17 18:38             ` Robert Love
  2005-06-17 18:45               ` Christoph Hellwig
  0 siblings, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-06-17 18:38 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Arnd Bergmann, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, 2005-06-17 at 19:28 +0100, Christoph Hellwig wrote:

> You are using ioctl as an really bad syscall multiplexer.  You're
> not using the file descriptor it's called on at all, so it does not qualify
> as a valid ioctl() usage even under the most lax rules.

We provide two different ioctl commands, it is not a bad multiplexer.
We have discussed this before.

We do use the fd.  It maps back to the inotify device.

> Also you claimed the resource shortage for the proposed architecture
> with just a single syscall, aka one watch per fd without showing any
> reasons why that would be true, in fact by any means there's no reason
> to believe file descriptors are a rare ressource in a modern Linux system.

It is not implausible to believe that a system might have the default
maximum for file descriptors (not very high) but allow a _much_ greater
number of inotify watches (32k, say).

That is our rationale.  I hear what you are saying, I understand it, and
at the end of the day I disagree.  I appreciate your input, but I feel
otherwise.

> I don't care whether you adopt my interface proposal or a different passable
> one, but the current one is not acceptable at all.

Everything to you is "really bad" and "totally unacceptable".  Chill
out.  Stop ranting so much and enjoy life.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 18:38             ` Robert Love
@ 2005-06-17 18:45               ` Christoph Hellwig
  2005-06-17 18:54                 ` Robert Love
  0 siblings, 1 reply; 45+ messages in thread
From: Christoph Hellwig @ 2005-06-17 18:45 UTC (permalink / raw)
  To: Robert Love
  Cc: Arnd Bergmann, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, Jun 17, 2005 at 02:38:06PM -0400, Robert Love wrote:
> > You are using ioctl as an really bad syscall multiplexer.  You're
> > not using the file descriptor it's called on at all, so it does not qualify
> > as a valid ioctl() usage even under the most lax rules.
> 
> We provide two different ioctl commands, it is not a bad multiplexer.
> We have discussed this before.
> 
> We do use the fd.  It maps back to the inotify device.

inotify does indeed use file->private data to retrieve the inotify_dev
structure.  Of which by design exists a single instance only.  So yes,
you do not use the file descriptor at all.

This really looks like copied from the ioctl abuse 101 example code.
We are beating up driver writers for stupidities like that all the time,
and inotify is not going to get a special treatment just because it's
core code.

> > Also you claimed the resource shortage for the proposed architecture
> > with just a single syscall, aka one watch per fd without showing any
> > reasons why that would be true, in fact by any means there's no reason
> > to believe file descriptors are a rare ressource in a modern Linux system.
> 
> It is not implausible to believe that a system might have the default
> maximum for file descriptors (not very high) but allow a _much_ greater
> number of inotify watches (32k, say).

And a default limit matters exactly how?

> Everything to you is "really bad" and "totally unacceptable".  Chill
> out.  Stop ranting so much and enjoy life.

Thanks a lot, I'm enjoying life a lot when I don't happen to have to deal
with ignorant people :)


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 18:45               ` Christoph Hellwig
@ 2005-06-17 18:54                 ` Robert Love
  0 siblings, 0 replies; 45+ messages in thread
From: Robert Love @ 2005-06-17 18:54 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Arnd Bergmann, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, 2005-06-17 at 19:45 +0100, Christoph Hellwig wrote:

> inotify does indeed use file->private data to retrieve the inotify_dev
> structure.  Of which by design exists a single instance only.  So yes,
> you do not use the file descriptor at all.

Sorry, I am not following how we "do not use the file descriptor at
all".  It is given to the ioctl and, as you said, used to find the
inotify_dev.  The file descriptor maps to the device, and vice versa.

You _can_ have multiple open devices per process.

> And a default limit matters exactly how?

Because we want people to actually use inotify, to make the desktop
better, and in the real world "become root and bump the fd limit" is not
always feasible.  The fd limit might be low for other reasons.

> Thanks a lot, I'm enjoying life a lot when I don't happen to have to deal
> with ignorant people :)

Just because we disagree does not make either of us ignorant.  Indeed,
even if one of us were wrong, we need not be ignorant--wrong word.
Neither of us is untaught on or unacquainted with the kernel.  We are
just viewing things differently.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 17:56       ` John McCutchan
@ 2005-06-17 21:33         ` Andrew Morton
  2005-06-17 21:40           ` Robert Love
  0 siblings, 1 reply; 45+ messages in thread
From: Andrew Morton @ 2005-06-17 21:33 UTC (permalink / raw)
  To: John McCutchan, Christoph Hellwig; +Cc: arnd, rml, zab, linux-kernel, viro

John McCutchan <ttb@tentacle.dhs.org> wrote:
>
> This was settled a long time ago. Robert, Andrew, and I had an off-list
> discussion months ago, and we all agreed that this was the right
> interface for inotify.

I don't think I ever really affirmatively agreed to anything.  I do recall
various things being discussed at various times and various things being
changed, but from where I sit it's all spread out and foggy.

I certainly remember that good-sounding recommendations which addressed the
things which Christoph doesn't like were convincingly shot down by yourself
and by Robert, but I don't recall why.

Look, this stuff is hard.  This is why I've asked you and Robert again and
again and again to generate some sort of design doc or FAQ which addresses
each of these frequently-asked-questions.  So the poor rest of us can look
through it and say "oh yeah".  Because inotify _is_ a tricky thing, and
standard kernel interface designs _don't_ fit it well.

So.  It's not too late.  Please spend an hour and write up the Inofity
Implementation FAQ?  You probably remember and fully understand what all of
our objections are and I know that you have explanations and rebuttals at
hand.

Please?  Something like:

q: Why does it use an ioctl multiplexer

a: Because ...

etc...

I haven't done a detailed review of the patch in months and I intend to do
another soon.  That FAQ will help!  When I ask more silly questions we can
update it, so those questions will never again be asked.

I know it's unusual process-wise, but inotify is an unusual feature.

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 21:33         ` Andrew Morton
@ 2005-06-17 21:40           ` Robert Love
  2005-06-17 23:52             ` Robert Love
  2005-06-18  0:05             ` Arnd Bergmann
  0 siblings, 2 replies; 45+ messages in thread
From: Robert Love @ 2005-06-17 21:40 UTC (permalink / raw)
  To: Andrew Morton
  Cc: John McCutchan, Christoph Hellwig, arnd, zab, linux-kernel, viro

On Fri, 2005-06-17 at 14:33 -0700, Andrew Morton wrote:

> So.  It's not too late.  Please spend an hour and write up the Inofity
> Implementation FAQ?  You probably remember and fully understand what all of
> our objections are and I know that you have explanations and rebuttals at
> hand.

I wrote this up the first time you asked:

	Documentation/filesystems/inotify.txt

It sounds like its never been addressed because some people don't take
different views as an acceptable solution.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 21:40           ` Robert Love
@ 2005-06-17 23:52             ` Robert Love
  2005-06-21  0:51               ` Neil Brown
  2005-06-18  0:05             ` Arnd Bergmann
  1 sibling, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-06-17 23:52 UTC (permalink / raw)
  To: Andrew Morton
  Cc: John McCutchan, Christoph Hellwig, arnd, zab, linux-kernel, viro

On Fri, 2005-06-17 at 17:40 -0400, Robert Love wrote:

> I wrote this up the first time you asked:
> 
> 	Documentation/filesystems/inotify.txt

If there is something I should add, just holler.  I will write an
encyclopedia if need be.

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 21:40           ` Robert Love
  2005-06-17 23:52             ` Robert Love
@ 2005-06-18  0:05             ` Arnd Bergmann
  2005-06-18  0:57               ` Robert Love
  1 sibling, 1 reply; 45+ messages in thread
From: Arnd Bergmann @ 2005-06-18  0:05 UTC (permalink / raw)
  To: Robert Love
  Cc: Andrew Morton, John McCutchan, Christoph Hellwig, zab,
	linux-kernel, viro

On Freedag 17 Juni 2005 23:40, Robert Love wrote:
> On Fri, 2005-06-17 at 14:33 -0700, Andrew Morton wrote:
> 
> > So.  It's not too late.  Please spend an hour and write up the Inofity
> > Implementation FAQ?  You probably remember and fully understand what all of
> > our objections are and I know that you have explanations and rebuttals at
> > hand.
> 
> I wrote this up the first time you asked:
> 
>         Documentation/filesystems/inotify.txt
> 
> It sounds like its never been addressed because some people don't take
> different views as an acceptable solution.

Yes, this is the file that I quoted in my mail. However, "because adding
three or four new system calls that mirrored open, close, and ioctl seemed
silly" doesn't really explain the choice.
An explanation along the lines of "neither ioctl on cdev nor a syscall
based approach is made everyone happy, so we decided to stick with the
one that is already used" might give a little more insight.

However, this still does not explain why the choice here is different
from epoll, as I clearly remember the decision to move from /dev/epoll
to the final sys_epoll* interface [1]. If it's just a matter of different
views like "ioctl is ugly" vs "syscall is silly", I would have gone for a
sys_inotify_create()/sys_inotify_ctl() based interface simply for the
reason of consistency with existing interfaces like epoll or mq_open().

Of course, at this point, compatibility with the existing user base
might already be a much stronger argument.

	Arnd <><

[1] http://lwn.net/Articles/13264/

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-18  0:05             ` Arnd Bergmann
@ 2005-06-18  0:57               ` Robert Love
  0 siblings, 0 replies; 45+ messages in thread
From: Robert Love @ 2005-06-18  0:57 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Andrew Morton, John McCutchan, Christoph Hellwig, zab,
	linux-kernel, viro

On Sat, 2005-06-18 at 02:05 +0200, Arnd Bergmann wrote:

> An explanation along the lines of "neither ioctl on cdev nor a syscall
> based approach is made everyone happy, so we decided to stick with the
> one that is already used" might give a little more insight.

I will add exactly this to the FAQ, thank you.

I suspect this is the situation that /dev/epoll faced, although in the
case of epoll, the opening the device did not have any real significance
(you would generally not restrict access and you would only open the
device once).

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 17:07     ` [patch] inotify Arnd Bergmann
  2005-06-17 17:54       ` Christoph Hellwig
  2005-06-17 17:56       ` John McCutchan
@ 2005-06-18  1:51       ` Chris Wedgwood
  2 siblings, 0 replies; 45+ messages in thread
From: Chris Wedgwood @ 2005-06-18  1:51 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Robert Love, Zach Brown, linux-kernel, Al Viro, John McCutchan,
	Andrew Morton

On Fri, Jun 17, 2005 at 07:07:38PM +0200, Arnd Bergmann wrote:

> in inotify is _very_ similar to how epoll is represented to user
> space. Is there a good reason that epoll is a set of syscalls while
> inotify is a character device, or is one of them simply wrong?

Tangentially inotify does some of what the DMAPI interface(s) want to
do (albeit somewhat simpler).  It would seem since DMAPI in it's
current incarnation won't "go away and die[1]" it might be worth
considering how much overlap there is.


[1] More than one fs has this out-of-tree and people are using it in
    various forms.

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-17 23:52             ` Robert Love
@ 2005-06-21  0:51               ` Neil Brown
  2005-06-21  2:15                 ` John McCutchan
  0 siblings, 1 reply; 45+ messages in thread
From: Neil Brown @ 2005-06-21  0:51 UTC (permalink / raw)
  To: Robert Love
  Cc: Andrew Morton, John McCutchan, Christoph Hellwig, arnd, zab,
	linux-kernel, viro

On Friday June 17, rml@novell.com wrote:
> On Fri, 2005-06-17 at 17:40 -0400, Robert Love wrote:
> 
> > I wrote this up the first time you asked:
> > 
> > 	Documentation/filesystems/inotify.txt
> 
> If there is something I should add, just holler.  I will write an
> encyclopedia if need be.

I haven't yet decided if I like it or not, but maybe I have some
useful comments to make...

> +
> +Q: What is the design decision behind using an-fd-per-device as
> +opposed to an fd-per-watch?
> +
> +A: An fd-per-watch quickly consumes more file descriptors than
> +are allowed, more fd's than are feasible to manage, and more
> +fd's than are ideally select()-able.  Yes, root can bump the
> +per-process fd limit and yes, users can use epoll, but requiring
> +both is silly and an extraneous requirement.  A watch consumes
> +less memory than an open file, separating the number spaces is
> +thus sensible.  The current design is what user-space developers
> +want: Users open the device, once, and add n watches, requiring
> +but one fd and no twiddling with fd limits.
> +Opening /dev/inotify two thousand times is silly.  If we can
> +implement user-space's preferences cleanly--and we can, the idr
> +layer makes stuff like this trivial--then we should.
> +


To address the particular points:
 - "An fd-per-watch quickly consumes more file descriptors than
    are allowed"

  I note that the current limit if 'wd's is 8192 per user, compared
  with the default 'fd' limit of 1024 per process.  So if a user has
  more than 8 processes making very heavy use of inotify, they would
  be better off with the file-descriptor limit...
  So I don't find this argument convincing.

  I would suggest that it be removed, and you put in a separate
  section discussion resource usage and limits.  You could explain why
  you don't use rlimit (probably not a hard case to win) and why the
  sysadmin cannot give different limits to different users (as a
  sys-admin, I would fight that), and why no one user would ever want
  more than about 8 processes using inotify :-)

 -  "more fd's than are feasible to manage,"

   It's not clear what this means.  The program will still need to
   manage lots of 'wd's.  How is this different from handling 'fd's?
   The only 'manage'ment issue I could come up with is the hassle of
   shepherding all these 'wd's through a 'fork', only to close them
   before an 'exec'.  Is that what you mean?  If so it would help to
   make it more explicit.

   Using the fd from /dev/inotify like a 'bag' of 'wd's which can be
   closed as a whole or passed around (through fork or exec or even
   over a unix domain socket) is, I think, I strong point.  You could
   possibly leverage that in selling the current inotify interface.


 - "and more fd's than are ideally select()-able"
  and "users can use epoll, but requiring [it] is silly and an
      extraneous requirement"

  Having to use epoll *should* not be a problem.
  "Surely" thought I "you an just select on the fd returned by
  epoll_create, and then adding inotify to your select() loop should
  be just as easy with epoll as with /dev/inotify".  But then I
  discovered that you cannot select() on the epoll fd :-(
  Maybe this should be fixed rather than be used as an excuse.


 - "A watch consumes less memory than an open filep"

   This is largely valid.  Two other possible responses:
   
   1/ Make the filep smaller.  Not every 'struct file' needs 
   read-ahead data (which is BIG).  Not every file needs the
   fown_struct.  I suspect that a small and generic struct file could
   be created which was embedded inside a larger less generic struct
   file when needed..  If the size if filep is really an issue.

   2/ An fd doesn't *have* to correspond to a 'struct file'.
   Suppose we allowed the 'watch_fd' systemcall to return an fd which
   actually mapped to a struct inotify_watch.  Possibly using a few
   high bits to differentiate different types of 'fds'.
   Most system calls would reject these fds immediately.  Those we
   wanted to handle them could easily be fixed.
   So sys_close would know how to close them.  sys_fcntl would need to be
   able to set close-on-exec.  Maybe a few others like poll.
   But from user-space, it would be 'just another fd'.

   You wouldn't be able to use these 'fds' in select(), but I think we
   can all agree that you probably wouldn't want to.

   It is also worth noting that there is a more telling reason to not
   use "struct file" than the size.  A 'struct file' points to a
   dentry.  A 'struct inotify_watch' points to an inode.  This
   difference is fairly crucial to how inotify works.
  

I hope some of these reflections are useful.

NeilBrown

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-21  0:51               ` Neil Brown
@ 2005-06-21  2:15                 ` John McCutchan
  2005-06-21  2:29                   ` Neil Brown
  0 siblings, 1 reply; 45+ messages in thread
From: John McCutchan @ 2005-06-21  2:15 UTC (permalink / raw)
  To: Neil Brown
  Cc: Robert Love, Andrew Morton, Christoph Hellwig, arnd, zab,
	linux-kernel, viro

On Tue, 2005-06-21 at 10:51 +1000, Neil Brown wrote:
> I haven't yet decided if I like it or not, but maybe I have some
> useful comments to make...
> 
> > +
> > +Q: What is the design decision behind using an-fd-per-device as
> > +opposed to an fd-per-watch?
> > +
> > +A: An fd-per-watch quickly consumes more file descriptors than
> > +are allowed, more fd's than are feasible to manage, and more
> > +fd's than are ideally select()-able.  Yes, root can bump the
> > +per-process fd limit and yes, users can use epoll, but requiring
> > +both is silly and an extraneous requirement.  A watch consumes
> > +less memory than an open file, separating the number spaces is
> > +thus sensible.  The current design is what user-space developers
> > +want: Users open the device, once, and add n watches, requiring
> > +but one fd and no twiddling with fd limits.
> > +Opening /dev/inotify two thousand times is silly.  If we can
> > +implement user-space's preferences cleanly--and we can, the idr
> > +layer makes stuff like this trivial--then we should.
> > +
> 
> 
> To address the particular points:
>  - "An fd-per-watch quickly consumes more file descriptors than
>     are allowed"
> 
>   I note that the current limit if 'wd's is 8192 per user, compared
>   with the default 'fd' limit of 1024 per process.  So if a user has
>   more than 8 processes making very heavy use of inotify, they would
>   be better off with the file-descriptor limit...
>   So I don't find this argument convincing.
> 
>   I would suggest that it be removed, and you put in a separate
>   section discussion resource usage and limits.  You could explain why
>   you don't use rlimit (probably not a hard case to win) and why the
>   sysadmin cannot give different limits to different users (as a
>   sys-admin, I would fight that), and why no one user would ever want
>   more than about 8 processes using inotify :-)
> 

Looking at beagle, it is a single process that can easily watch more
than 1024 directories. Beagle would have to work around the 1024 limit
by managing processes, each of which watch a fraction of the total
watches. PITA. Also, 8192 was an arbitrary choice that seemed big enough
for most users needs and all of these limits can be set in sysfs.
Checkout /sys/misc/inotify.

>  -  "more fd's than are feasible to manage,"
> 
>    It's not clear what this means.  The program will still need to
>    manage lots of 'wd's.  How is this different from handling 'fd's?
>    The only 'manage'ment issue I could come up with is the hassle of
>    shepherding all these 'wd's through a 'fork', only to close them
>    before an 'exec'.  Is that what you mean?  If so it would help to
>    make it more explicit.
> 

The program doesn't really have to manage the wd's. A program simply
needs a map from wd to it's own structure.

Assume for a moment that we had chosen a fd-as-watch approach. Now take
beagle, which has tons of watches. Every time beagle goes through it's
loop, it has to add each of those watch-fd's to the select table, then
check for each one afterwards. It's pretty easy to see how this is
unmanageable and a waste of CPU time. 


>    Using the fd from /dev/inotify like a 'bag' of 'wd's which can be
>    closed as a whole or passed around (through fork or exec or even
>    over a unix domain socket) is, I think, I strong point.  You could
>    possibly leverage that in selling the current inotify interface.
> 

This is a great way of thinking about it.

> 
>  - "A watch consumes less memory than an open filep"
> 
>    This is largely valid.  Two other possible responses:
>    
>    1/ Make the filep smaller.  Not every 'struct file' needs 
>    read-ahead data (which is BIG).  Not every file needs the
>    fown_struct.  I suspect that a small and generic struct file could
>    be created which was embedded inside a larger less generic struct
>    file when needed..  If the size if filep is really an issue.
> 
>

Maybe a good idea, but this is overkill for inotify.

>    2/ An fd doesn't *have* to correspond to a 'struct file'.
>    Suppose we allowed the 'watch_fd' systemcall to return an fd which
>    actually mapped to a struct inotify_watch.  Possibly using a few
>    high bits to differentiate different types of 'fds'.
>    Most system calls would reject these fds immediately.  Those we
>    wanted to handle them could easily be fixed.
>    So sys_close would know how to close them.  sys_fcntl would need to be
>    able to set close-on-exec.  Maybe a few others like poll.
>    But from user-space, it would be 'just another fd'.
>
>

Why bother though? The idr layer lets us have a integer that maps to a 
data structure in kernel space, pretty much for free. 


-- 
John McCutchan <ttb@tentacle.dhs.org>

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-21  2:15                 ` John McCutchan
@ 2005-06-21  2:29                   ` Neil Brown
  2005-06-21  2:43                     ` John McCutchan
  2005-06-21 15:55                     ` Robert Love
  0 siblings, 2 replies; 45+ messages in thread
From: Neil Brown @ 2005-06-21  2:29 UTC (permalink / raw)
  To: John McCutchan
  Cc: Robert Love, Andrew Morton, Christoph Hellwig, arnd, zab,
	linux-kernel, viro

On Monday June 20, ttb@tentacle.dhs.org wrote:
> On Tue, 2005-06-21 at 10:51 +1000, Neil Brown wrote:
> > I haven't yet decided if I like it or not, but maybe I have some
> > useful comments to make...
> > 
> > > +
> > > +Q: What is the design decision behind using an-fd-per-device as
> > > +opposed to an fd-per-watch?
> > > +
> > > +A: An fd-per-watch quickly consumes more file descriptors than
> > > +are allowed, more fd's than are feasible to manage, and more
> > > +fd's than are ideally select()-able.  Yes, root can bump the
> > > +per-process fd limit and yes, users can use epoll, but requiring
> > > +both is silly and an extraneous requirement.  A watch consumes
> > > +less memory than an open file, separating the number spaces is
> > > +thus sensible.  The current design is what user-space developers
> > > +want: Users open the device, once, and add n watches, requiring
> > > +but one fd and no twiddling with fd limits.
> > > +Opening /dev/inotify two thousand times is silly.  If we can
> > > +implement user-space's preferences cleanly--and we can, the idr
> > > +layer makes stuff like this trivial--then we should.
> > > +
> > 
> > 
> > To address the particular points:
> >  - "An fd-per-watch quickly consumes more file descriptors than
> >     are allowed"
> > 
> >   I note that the current limit if 'wd's is 8192 per user, compared
> >   with the default 'fd' limit of 1024 per process.  So if a user has
> >   more than 8 processes making very heavy use of inotify, they would
> >   be better off with the file-descriptor limit...
> >   So I don't find this argument convincing.
> > 
> >   I would suggest that it be removed, and you put in a separate
> >   section discussion resource usage and limits.  You could explain why
> >   you don't use rlimit (probably not a hard case to win) and why the
> >   sysadmin cannot give different limits to different users (as a
> >   sys-admin, I would fight that), and why no one user would ever want
> >   more than about 8 processes using inotify :-)
> > 
> 
> Looking at beagle, it is a single process that can easily watch more
> than 1024 directories. Beagle would have to work around the 1024 limit
> by managing processes, each of which watch a fraction of the total
> watches. PITA. Also, 8192 was an arbitrary choice that seemed big enough
> for most users needs and all of these limits can be set in sysfs.
> Checkout /sys/misc/inotify.


I think you missed my point...
The FAQ entry which I found unconvincing said

"Yes, root can bump the per-process fd limit ... but requiring [this]
 is silly and an extraneous requirement." 

So when you say "... all of these limits can be set in sysfs", you are
effectively contradicting the FAQ entry.
Yes, 1024 is an arbitrary limit.  But so is 8192.
The argument "We cannot use fd's because the arbitrary limit is lower
than the arbitrary limit that I want to use" simply doesn't hold
water.
There may well be other good arguments against 'fd's, but I'm trying
to point out that this isn't one of them, and so shouldn't appear in
this part of the FAQ.


> 
> >  -  "more fd's than are feasible to manage,"
> > 
> >    It's not clear what this means.  The program will still need to
> >    manage lots of 'wd's.  How is this different from handling 'fd's?
> >    The only 'manage'ment issue I could come up with is the hassle of
> >    shepherding all these 'wd's through a 'fork', only to close them
> >    before an 'exec'.  Is that what you mean?  If so it would help to
> >    make it more explicit.
> > 
> 
> The program doesn't really have to manage the wd's. A program simply
> needs a map from wd to it's own structure.

So I think you are agreeing with me that the text "more fd's than are
feasible to manage," should be removed from the FAQ?  Thanks.

> 
> Assume for a moment that we had chosen a fd-as-watch approach. Now take
> beagle, which has tons of watches. Every time beagle goes through it's
> loop, it has to add each of those watch-fd's to the select table, then
> check for each one afterwards. It's pretty easy to see how this is
> unmanageable and a waste of CPU time. 

I don't think there can be any question that having to tell select
about each 'watch' is unreasonable.

> 
> Why bother though? The idr layer lets us have a integer that maps to a 
> data structure in kernel space, pretty much for free. 
> 
Because some people think that creating a whole new abstraction when
we have one (fds) that seem to fit the bill, is the wrong thing to do.
I don't currently have an opinion on that, but I am trying to explore
options with a bit of lateral thinking.

NeilBrown

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-21  2:29                   ` Neil Brown
@ 2005-06-21  2:43                     ` John McCutchan
  2005-06-21 15:55                     ` Robert Love
  1 sibling, 0 replies; 45+ messages in thread
From: John McCutchan @ 2005-06-21  2:43 UTC (permalink / raw)
  To: Neil Brown
  Cc: Robert Love, Andrew Morton, Christoph Hellwig, arnd, zab,
	linux-kernel, viro

On Tue, 2005-06-21 at 12:29 +1000, Neil Brown wrote:
> On Monday June 20, ttb@tentacle.dhs.org wrote:
> > On Tue, 2005-06-21 at 10:51 +1000, Neil Brown wrote:
> > > I haven't yet decided if I like it or not, but maybe I have some
> > > useful comments to make...
> > > 
> > > > +
> > > > +Q: What is the design decision behind using an-fd-per-device as
> > > > +opposed to an fd-per-watch?
> > > > +
> > > > +A: An fd-per-watch quickly consumes more file descriptors than
> > > > +are allowed, more fd's than are feasible to manage, and more
> > > > +fd's than are ideally select()-able.  Yes, root can bump the
> > > > +per-process fd limit and yes, users can use epoll, but requiring
> > > > +both is silly and an extraneous requirement.  A watch consumes
> > > > +less memory than an open file, separating the number spaces is
> > > > +thus sensible.  The current design is what user-space developers
> > > > +want: Users open the device, once, and add n watches, requiring
> > > > +but one fd and no twiddling with fd limits.
> > > > +Opening /dev/inotify two thousand times is silly.  If we can
> > > > +implement user-space's preferences cleanly--and we can, the idr
> > > > +layer makes stuff like this trivial--then we should.
> > > > +
> > > 
> > > 
> > > To address the particular points:
> > >  - "An fd-per-watch quickly consumes more file descriptors than
> > >     are allowed"
> > > 
> > >   I note that the current limit if 'wd's is 8192 per user, compared
> > >   with the default 'fd' limit of 1024 per process.  So if a user has
> > >   more than 8 processes making very heavy use of inotify, they would
> > >   be better off with the file-descriptor limit...
> > >   So I don't find this argument convincing.
> > > 
> > >   I would suggest that it be removed, and you put in a separate
> > >   section discussion resource usage and limits.  You could explain why
> > >   you don't use rlimit (probably not a hard case to win) and why the
> > >   sysadmin cannot give different limits to different users (as a
> > >   sys-admin, I would fight that), and why no one user would ever want
> > >   more than about 8 processes using inotify :-)
> > > 
> > 
> > Looking at beagle, it is a single process that can easily watch more
> > than 1024 directories. Beagle would have to work around the 1024 limit
> > by managing processes, each of which watch a fraction of the total
> > watches. PITA. Also, 8192 was an arbitrary choice that seemed big enough
> > for most users needs and all of these limits can be set in sysfs.
> > Checkout /sys/misc/inotify.
> 
> 
> I think you missed my point...
> The FAQ entry which I found unconvincing said
> 
> "Yes, root can bump the per-process fd limit ... but requiring [this]
>  is silly and an extraneous requirement." 
> 
> So when you say "... all of these limits can be set in sysfs", you are
> effectively contradicting the FAQ entry.
> Yes, 1024 is an arbitrary limit.  But so is 8192.
> The argument "We cannot use fd's because the arbitrary limit is lower
> than the arbitrary limit that I want to use" simply doesn't hold
> water.
> There may well be other good arguments against 'fd's, but I'm trying
> to point out that this isn't one of them, and so shouldn't appear in
> this part of the FAQ.
> 

Fair enough. 

But, because the fd limit is established at 1024 and we will want the wd
limit to be much higher than the current fd limit, we do need our own
limit. fd's and wd's represent different things, why confuse the two
limits?

> 
> > 
> > >  -  "more fd's than are feasible to manage,"
> > > 
> > >    It's not clear what this means.  The program will still need to
> > >    manage lots of 'wd's.  How is this different from handling 'fd's?
> > >    The only 'manage'ment issue I could come up with is the hassle of
> > >    shepherding all these 'wd's through a 'fork', only to close them
> > >    before an 'exec'.  Is that what you mean?  If so it would help to
> > >    make it more explicit.
> > > 
> > 
> > The program doesn't really have to manage the wd's. A program simply
> > needs a map from wd to it's own structure.
> 
> So I think you are agreeing with me that the text "more fd's than are
> feasible to manage," should be removed from the FAQ?  Thanks.
> 

No, I'm not. An application would have to manage them if they were fd's.
My select() example in the previous mail explains.

> > 
> > Assume for a moment that we had chosen a fd-as-watch approach. Now take
> > beagle, which has tons of watches. Every time beagle goes through it's
> > loop, it has to add each of those watch-fd's to the select table, then
> > check for each one afterwards. It's pretty easy to see how this is
> > unmanageable and a waste of CPU time. 
> 
> I don't think there can be any question that having to tell select
> about each 'watch' is unreasonable.
> 

And if watches were represented as fd's we would have to do just that.

> > 
> > Why bother though? The idr layer lets us have a integer that maps to a 
> > data structure in kernel space, pretty much for free. 
> > 
> Because some people think that creating a whole new abstraction when
> we have one (fds) that seem to fit the bill, is the wrong thing to do.
> I don't currently have an opinion on that, but I am trying to explore
> options with a bit of lateral thinking.

I appreciate that you are trying to explore alternatives. I have done
the same, and come to the conclusion that the current interface is the
best approach. It is friendly to both the kernel and the user space
programs which use inotify.

-- 
John McCutchan <ttb@tentacle.dhs.org>

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-21  2:29                   ` Neil Brown
  2005-06-21  2:43                     ` John McCutchan
@ 2005-06-21 15:55                     ` Robert Love
  2005-07-14  0:25                       ` Neil Brown
  1 sibling, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-06-21 15:55 UTC (permalink / raw)
  To: Neil Brown
  Cc: John McCutchan, Andrew Morton, Christoph Hellwig, arnd, zab,
	linux-kernel, viro

On Tue, 2005-06-21 at 12:29 +1000, Neil Brown wrote:

> There may well be other good arguments against 'fd's, but I'm trying
> to point out that this isn't one of them, and so shouldn't appear in
> this part of the FAQ.

You raise a good point, although one could argue that raising the fd
limit is not necessarily feasible.

There are other good arguments.  With a single fd, there is a single
item to block on, which is mapped to a single queue of events.  The
single fd returns all watch events and also any potential out-of-band
data.  If every fd was a separate watch,

	- There would be no way to get event ordering.  Events on file
	  foo and file bar would pop poll() on both fd's, but there
	  would be no way to tell which happened first.  A single queue
	  trivially gives you ordering.
	- We'd have to maintain n fd's and n internal queues with state,
	  versus just one.  It is a lot messier in the kernel.
	- User-space developers prefer the current API.  The Beagle
	  guys, for example, love it.  Trust me, I asked.  It is not
	  a surprise: Who'd want to manage and block on 1000 fd's?
	- You'd have to manage the fd's, as an example: call close()
	  when you received a delete event.
	- No way to get out of band data.
	- 1024 is still too low.  ;-)

When you talk about designing a file change notification system that
scales to 1000s of directories, juggling 1000s of fd's just does not
seem the right interface.  It is too heavy.

I should add this to the FAQ, yes.  ;-)

	Robert Love



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-06-21 15:55                     ` Robert Love
@ 2005-07-14  0:25                       ` Neil Brown
  2005-07-14  4:11                         ` John McCutchan
  0 siblings, 1 reply; 45+ messages in thread
From: Neil Brown @ 2005-07-14  0:25 UTC (permalink / raw)
  To: Robert Love
  Cc: John McCutchan, Andrew Morton, Christoph Hellwig, arnd, zab,
	linux-kernel, viro


On Tuesday June 21, rml@novell.com wrote:

(Ok, so 3 weeks is too long (leave, family....), and inotify has been
accepted into mainline, so maybe it isn't worth pursuing this
discussion, but there are a couple of points I'd still like to make,
or respond to....)

> On Tue, 2005-06-21 at 12:29 +1000, Neil Brown wrote:
> 
> > There may well be other good arguments against 'fd's, but I'm trying
> > to point out that this isn't one of them, and so shouldn't appear in
> > this part of the FAQ.
> 
> You raise a good point, although one could argue that raising the fd
> limit is not necessarily feasible.
> 
> There are other good arguments.  With a single fd, there is a single
> item to block on, which is mapped to a single queue of events.  The
> single fd returns all watch events and also any potential out-of-band
> data.  If every fd was a separate watch,
> 
> 	- There would be no way to get event ordering.  Events on file
> 	  foo and file bar would pop poll() on both fd's, but there
> 	  would be no way to tell which happened first.  A single queue
> 	  trivially gives you ordering.

"first"? Two processes on two processors modify two files both in the
same timeslice.  Which changed "first" ?
Timestamps is a well-proven mechanism for determining ordering, and
your current 'cookie' is very far from a timestamp (or sequence-stamp,
which would be as good).


> 	- We'd have to maintain n fd's and n internal queues with state,
> 	  versus just one.  It is a lot messier in the kernel.

You don't maintain just one queue. You maintain one per open of
/dev/inotify.  So it is n vs m, not n vs 1.

> 	- User-space developers prefer the current API.  The Beagle
> 	  guys, for example, love it.  Trust me, I asked.  It is not
> 	  a surprise: Who'd want to manage and block on 1000 fd's?

But they still need to track 1000s of 'wd's.  I'm sure the right
library support would make any reasonable kernel interface easy to use
for application developers.

> 	- You'd have to manage the fd's, as an example: call close()
> 	  when you received a delete event.

This is not rocket science !

> 	- No way to get out of band data.

Not sure what you mean here.. Maybe IN_Q_OVERFLOW...

I first thought you might mean the filenames for directory events, but
then thought maybe not.  However while I was thinking this I also
thought that your inotify_event structure doesn't seem to be designed
for extension.
I recall you saying earlier that you had rejected requests for
byte-range information about read/write, but that it could be open for
reconsideration after inotify was well established in the kernel.
Suppose you were to give in and provide such support, how would it
appear in inotify_event? 

I would have put the "len" at the start of the packet and have it give
the length of the whole packet.  Then 'mask' would list both the
events and the accompanying fields.  One bit to say "cookie is
present" (it currently is very rarely meaningful, but always present),
one bit for 'name is present' (I'm not sure how you are currently
meant to tell if the name should be considered or not. maybe you have
to know if 'wd' refers to a directory). Other bits for 'byterange is
present', 'pid is present', 'uid is present', ....

> 	- 1024 is still too low.  ;-)
> 
> When you talk about designing a file change notification system that
> scales to 1000s of directories, juggling 1000s of fd's just does not
> seem the right interface.  It is too heavy.
> 

So I FINALLY asked myself the question "Why does he want 1000s of directories"
and I come up with the answer "he probably doesn't".

What I suspect you really want to handle is a few objects where each
object is either a file, a directory, or a directory tree.
1000's of directories sounds to me like you really want to monitor all
the directories in a directory tree.  But you don't even actually want
that.  You really want to watch all the files in all the directories
in a directory tree (do tell me if I'm wrong).

Suppose you were to arrange that whenever a dentry is created or
walked: if the parent has an inotify_watch attached with a IN_RECURSE
flag, then an appropriate inotify_watch was attached to the child
(unless already present).  It would be attached in such a way that it
didn't hold a reference to the inode and so the inode could still be
discarded when inactive.

This would (with a bit more work) allow you to get events for
everything in a directory tree off just one filedescriptor.

You would need to be careful about allowing for sub-trees to be
renamed out of the main tree, but I don't see that as insurmountable. 

This approach has the benefit that you don't lock 1000's of inodes
into memory when you aren't really interested in them.

So my preferred interface would be something like:

   fd1 = open("/file/or/tree/i/want/to/watch", O_XXX)
   fd2 = sys_tnotify(fd1, BIT|MASK|OF|EVENTS(|TN_RECURSE),
         ...otherstuff);
  
   if TN_RECURSE
       ftw(fd1...)
       /* This is needed partly because we need to make sure an
       inotify_watch is attached to any child already in cache,
       and partly because learning about changes isn't much use unless
       we have looked at the initial state
       */
 

   ....
    select/poll/epoll on fd2
      read(fd2, buf, maxbytes) returns a whole number of records

    each record is:
         __u32 record_size
         __u32 events_and_fields /* lists the details of this record
				  * all other fields are optional and
				  * only present if the relevant bit
				  * is set
				  */
         __u64 inum
	 __u32 cookie
         char name[] /* null terminated, 4byte padded */
         ....

     name[] would be a path name from fd1 down to the affected object.
     This would probably be unreliable or even non-existent if a
     containing directory have been moved since (between event and
     read), but you would eventually see the directory move, and
     should then ftw the directory in the new location and check for
     interesting events "the old way".


One last little nit: The name "inotify" suggests to me that it is an
inode-based notification scheme rather than a directory-entry-based
notification scheme.  However if I'm watching a directory and one of
the files is changed though a hardlink to a different directory, or
via NFS, then I may not see the change.  I realise this is not really
fixable.  I assume it is/will be mentioned in the relevant
documentation.


NeilBrown


PS I just noticed inotify.h spell writable as writtable :-)

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-07-14  0:25                       ` Neil Brown
@ 2005-07-14  4:11                         ` John McCutchan
  0 siblings, 0 replies; 45+ messages in thread
From: John McCutchan @ 2005-07-14  4:11 UTC (permalink / raw)
  To: Neil Brown
  Cc: Robert Love, Andrew Morton, Christoph Hellwig, arnd, zab,
	linux-kernel, viro

On Thu, 2005-07-14 at 10:25 +1000, Neil Brown wrote:
> On Tuesday June 21, rml@novell.com wrote:
> > On Tue, 2005-06-21 at 12:29 +1000, Neil Brown wrote:
> > 
> > > There may well be other good arguments against 'fd's, but I'm trying
> > > to point out that this isn't one of them, and so shouldn't appear in
> > > this part of the FAQ.
> > 
> > You raise a good point, although one could argue that raising the fd
> > limit is not necessarily feasible.
> > 
> > There are other good arguments.  With a single fd, there is a single
> > item to block on, which is mapped to a single queue of events.  The
> > single fd returns all watch events and also any potential out-of-band
> > data.  If every fd was a separate watch,
> > 
> > 	- There would be no way to get event ordering.  Events on file
> > 	  foo and file bar would pop poll() on both fd's, but there
> > 	  would be no way to tell which happened first.  A single queue
> > 	  trivially gives you ordering.
> 
> "first"? Two processes on two processors modify two files both in the
> same timeslice.  Which changed "first" ?

Yeah, you're right, when we are dealing with a tiny time slice on an SMP
system, you can't really tell which came first. But this isn't the case
we are trying to solve. We are trying to make things work on a larger
(but still relatively small) time scale. Consider mv a b; mv b c; mv a
c; If we had seperate event queues for a,b, and c, we would have no
straight forward way to determine what order those events happened in.

> Timestamps is a well-proven mechanism for determining ordering, and
> your current 'cookie' is very far from a timestamp (or sequence-stamp,
> which would be as good).

The cookie is never used as a way of determining order.

> 
> 
> > 	- We'd have to maintain n fd's and n internal queues with state,
> > 	  versus just one.  It is a lot messier in the kernel.
> 
> You don't maintain just one queue. You maintain one per open of
> /dev/inotify.  So it is n vs m, not n vs 1.
> 
> > 	- User-space developers prefer the current API.  The Beagle
> > 	  guys, for example, love it.  Trust me, I asked.  It is not
> > 	  a surprise: Who'd want to manage and block on 1000 fd's?
> 
> But they still need to track 1000s of 'wd's.  I'm sure the right
> library support would make any reasonable kernel interface easy to use
> for application developers.
> 

Keeping track of a wd consists of mapping the wd to a structure. Keeping
track of fds mean: mapping the fd to a structure, and for every loop:

add all the fd's to FD_SET
select on fd's
check EACH fd if it has events to be read.

and when app exists, close all fd's.

This is obviously a no go.

> > 	- No way to get out of band data.
> 
> Not sure what you mean here.. Maybe IN_Q_OVERFLOW...
> 
> I first thought you might mean the filenames for directory events, but
> then thought maybe not.  However while I was thinking this I also
> thought that your inotify_event structure doesn't seem to be designed
> for extension.
> I recall you saying earlier that you had rejected requests for
> byte-range information about read/write, but that it could be open for
> reconsideration after inotify was well established in the kernel.
> Suppose you were to give in and provide such support, how would it
> appear in inotify_event? 
> 
> I would have put the "len" at the start of the packet and have it give
> the length of the whole packet.  Then 'mask' would list both the
> events and the accompanying fields.  One bit to say "cookie is
> present" (it currently is very rarely meaningful, but always present),
> one bit for 'name is present' (I'm not sure how you are currently
> meant to tell if the name should be considered or not. maybe you have
> to know if 'wd' refers to a directory). Other bits for 'byterange is
> present', 'pid is present', 'uid is present', ....
> 

You raise a good point here. No apps currently using inotify expect it
to be 100% stable, so now is the time to fix this.


> > 	- 1024 is still too low.  ;-)
> > 
> > When you talk about designing a file change notification system that
> > scales to 1000s of directories, juggling 1000s of fd's just does not
> > seem the right interface.  It is too heavy.
> > 
> 
> So I FINALLY asked myself the question "Why does he want 1000s of directories"
> and I come up with the answer "he probably doesn't".
> 
> What I suspect you really want to handle is a few objects where each
> object is either a file, a directory, or a directory tree.
> 1000's of directories sounds to me like you really want to monitor all
> the directories in a directory tree.  But you don't even actually want
> that.  You really want to watch all the files in all the directories
> in a directory tree (do tell me if I'm wrong).
> 
> Suppose you were to arrange that whenever a dentry is created or
> walked: if the parent has an inotify_watch attached with a IN_RECURSE
> flag, then an appropriate inotify_watch was attached to the child
> (unless already present).  It would be attached in such a way that it
> didn't hold a reference to the inode and so the inode could still be
> discarded when inactive.
> 

An inotify_watch has to map back to the inotify context that created it.
Where would the events for this inotify context go? Not to mention the
over head.

> This would (with a bit more work) allow you to get events for
> everything in a directory tree off just one filedescriptor.
> 

So say I create the initial watch at '/' with the recursive flag set,
then something happens in /home/user/xxxxx/yyyyyy/zzzzzz/dddddd/ for the
sake of the argument, lets say a file is being downloaded call foo.

Each time foo is written to, the kernel needs to traverse up the
directory tree to build the full path name, allocate the memory for the
event and queue it up.

The over head in constructing the full path name is unacceptable, and
the memory needs of this approach would get huge, especially if the
watcher didn't bother to read events from the queue.

For recursive watching to happen, you either need to walk down the
directory tree at the time of watch creation, or walk up the directory
tree for each event. Take your pick. Neither seems acceptable to me.

> You would need to be careful about allowing for sub-trees to be
> renamed out of the main tree, but I don't see that as insurmountable. 
> 
> This approach has the benefit that you don't lock 1000's of inodes
> into memory when you aren't really interested in them.
> 

Yeah, you just waste tons of time constructing full path names and waste
lots of memory saving those names around until the events are read.


> 
> One last little nit: The name "inotify" suggests to me that it is an
> inode-based notification scheme rather than a directory-entry-based
> notification scheme.  However if I'm watching a directory and one of
> the files is changed though a hardlink to a different directory, or
> via NFS, then I may not see the change.  I realise this is not really
> fixable.  I assume it is/will be mentioned in the relevant
> documentation.
> 

This will be added to the Documentation.

> 
> NeilBrown
> 
> 
> PS I just noticed inotify.h spell writable as writtable :-)

Thanks.



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [patch] inotify.
  2005-05-09 16:05 Robert Love
@ 2005-05-09 17:43 ` Coywolf Qi Hunt
  0 siblings, 0 replies; 45+ messages in thread
From: Coywolf Qi Hunt @ 2005-05-09 17:43 UTC (permalink / raw)
  To: Robert Love; +Cc: Al Viro, linux-kernel, Andrew Morton, John McCutchan

On 5/10/05, Robert Love <rml@novell.com> wrote:
> Below is the latest revision of inotify, against 2.6.12-rc4.
> 
> The only functional changes from the last release is a compile warning
> fix for !CONFIG_INOTIFY.

Here's another compile warning, also in the current -mm tree.

> diff -urN linux-2.6.12-rc4/fs/namei.c linux/fs/namei.c
> --- linux-2.6.12-rc4/fs/namei.c 2005-05-09 11:52:48.000000000 -0400
> +++ linux/fs/namei.c    2005-05-09 11:58:52.000000000 -0400

...

> @@ -2172,18 +2174,18 @@
>         DQUOT_INIT(old_dir);
>         DQUOT_INIT(new_dir);
> 
> +       old_name = fsnotify_oldname_init(old_dentry->d_name.name);
> +

prodigy:/home/coywolf/2.6.12-rc3-mm3-cy# make fs/namei.o
  CC      fs/namei.o
/home/coywolf/2.6.12-rc3-mm3-cy/fs/namei.c: In function `vfs_rename':
/home/coywolf/2.6.12-rc3-mm3-cy/fs/namei.c:2177: warning: passing arg
1 of `fsnotify_oldname_init' from incompatible pointer type


The problem lies in const char *name V.S. const unsigned char *name.


-- 
Coywolf Qi Hunt
http://sosdg.org/~coywolf/

^ permalink raw reply	[flat|nested] 45+ messages in thread

* [patch] inotify.
@ 2005-05-09 16:05 Robert Love
  2005-05-09 17:43 ` Coywolf Qi Hunt
  0 siblings, 1 reply; 45+ messages in thread
From: Robert Love @ 2005-05-09 16:05 UTC (permalink / raw)
  To: Al Viro, linux-kernel; +Cc: Andrew Morton, John McCutchan

Below is the latest revision of inotify, against 2.6.12-rc4.

The only functional changes from the last release is a compile warning
fix for !CONFIG_INOTIFY.

This should be identical to what will be in the next 2.6-mm.

See Documentation/filesystems/inotify.txt for usage information and
design decision rationale.

Cheers,

	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),
Gamin (a FAM replacement), and other projects.

Signed-off-by: Robert Love <rml@novell.com>

 Documentation/filesystems/inotify.txt |  123 ++++
 fs/Kconfig                            |   13 
 fs/Makefile                           |    1 
 fs/attr.c                             |   33 -
 fs/compat.c                           |   12 
 fs/file_table.c                       |    3 
 fs/inode.c                            |    6 
 fs/inotify.c                          |  981 ++++++++++++++++++++++++++++++++++
 fs/namei.c                            |   30 -
 fs/open.c                             |    4 
 fs/read_write.c                       |   15 
 fs/xattr.c                            |    5 
 include/linux/fs.h                    |    6 
 include/linux/fsnotify.h              |  257 ++++++++
 include/linux/inotify.h               |  124 ++++
 include/linux/sched.h                 |    4 
 kernel/user.c                         |    4 
 17 files changed, 1565 insertions(+), 56 deletions(-)

diff -urN linux-2.6.12-rc4/Documentation/filesystems/inotify.txt linux/Documentation/filesystems/inotify.txt
--- linux-2.6.12-rc4/Documentation/filesystems/inotify.txt	1969-12-31 19:00:00.000000000 -0500
+++ linux/Documentation/filesystems/inotify.txt	2005-05-09 11:58:52.000000000 -0400
@@ -0,0 +1,123 @@
+				    inotify
+	     a powerful yet simple file change notification system
+
+
+
+Document started 15 Mar 2005 by Robert Love <rml@novell.com>
+
+(i) User Interface
+
+Inotify is controlled by a device node, /dev/inotify.  If you do not use udev,
+this device may need to be created manually.  First step, open it
+
+	int dev_fd = open ("/dev/inotify", O_RDONLY);
+
+Change events are managed by "watches".  A watch is an (object,mask) pair where
+the object is a file or directory and the mask is a bitmask of one or more
+inotify events that the application wishes to receive.  See <linux/inotify.h>
+for valid events.  A watch is referenced by a watch descriptor, or wd.
+
+Watches are added via a file descriptor.
+
+Watches on a directory will return events on any files inside of the directory.
+
+Adding a watch is simple,
+
+	/* 'wd' represents the watch on fd with mask */
+	struct inotify_request req = { fd, mask };
+	int wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+You can add a large number of files via something like
+
+	for each file to watch {
+		struct inotify_request req;
+		int file_fd;
+
+		file_fd = open (file, O_RDONLY);
+		if (fd < 0) {
+			perror ("open");
+			break;
+		}
+
+		req.fd = file_fd;
+		req.mask = mask;
+
+		wd = ioctl (dev_fd, INOTIFY_WATCH, &req);
+
+		close (fd);
+	}
+
+You can update an existing watch in the same manner, by passing in a new mask.
+
+An existing watch is removed via the INOTIFY_IGNORE ioctl, for example
+
+	ioctl (dev_fd, INOTIFY_IGNORE, wd);
+
+Events are provided in the form of an inotify_event structure that is read(2)
+from /dev/inotify.  The filename is of dynamic length and follows the struct.
+It is of size len.  The filename is padded with null bytes to ensure proper
+alignment.  This padding is reflected in len.
+
+You can slurp multiple events by passing a large buffer, for example
+
+	size_t len = read (fd, buf, BUF_LEN);
+
+Will return as many events as are available and fit in BUF_LEN.
+
+/dev/inotify is also select() and poll() able.
+
+You can find the size of the current event queue via the FIONREAD ioctl.
+
+All watches are destroyed and cleaned up on close.
+
+
+(ii) Internal Kernel Implementation
+
+Each open inotify device is associated with an inotify_device structure.
+
+Each watch is associated with an inotify_watch structure.  Watches are chained
+off of each associated device and each associated inode.
+
+See fs/inotify.c for the locking and lifetime rules.
+
+
+(iii) Rationale
+
+Q: What is the design decision behind not tying the watch to the
+open fd of the watched object?
+
+A: Watches are associated with an open inotify device, not an
+open file.  This solves the primary problem with dnotify:
+keeping the file open pins the file and thus, worse, pins the
+mount.  Dnotify is therefore infeasible for use on a desktop
+system with removable media as the media cannot be unmounted.
+
+Q: What is the design decision behind using an-fd-per-device as
+opposed to an fd-per-watch?
+
+A: An fd-per-watch quickly consumes more file descriptors than
+are allowed, more fd's than are feasible to manage, and more
+fd's than are ideally select()-able.  Yes, root can bump the
+per-process fd limit and yes, users can use epoll, but requiring
+both is silly and an extraneous requirement.  A watch consumes
+less memory than an open file, separating the number spaces is
+thus sensible.  The current design is what user-space developers
+want: Users open the device, once, and add n watches, requiring
+but one fd and no twiddling with fd limits.
+Opening /dev/inotify two thousand times is silly.  If we can
+implement user-space's preferences cleanly--and we can, the idr
+layer makes stuff like this trivial--then we should.
+
+Q: Why a device node?
+
+A: The second biggest problem with dnotify is that the user
+interface sucks ass.  Signals are a terrible, terrible interface
+for file notification.  Or for anything, for that matter.  The
+idea solution, from all perspectives, is a file descriptor based
+one that allows basic file I/O and poll/select.  Obtaining the
+fd and managing the watches could of been done either via a
+device file or a family of new system calls.  We decided to
+implement a device file because adding three or four new system
+calls that mirrored open, close, and ioctl seemed silly.  A
+character device makes sense from user-space and was easy to
+implement inside of the kernel.
diff -urN linux-2.6.12-rc4/fs/attr.c linux/fs/attr.c
--- linux-2.6.12-rc4/fs/attr.c	2005-05-09 11:52:48.000000000 -0400
+++ linux/fs/attr.c	2005-05-09 11:58:52.000000000 -0400
@@ -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 @@
 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 @@
 	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.12-rc4/fs/compat.c linux/fs/compat.c
--- linux-2.6.12-rc4/fs/compat.c	2005-05-09 11:52:48.000000000 -0400
+++ linux/fs/compat.c	2005-05-09 11:58:52.000000000 -0400
@@ -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 @@
 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 -urN linux-2.6.12-rc4/fs/file_table.c linux/fs/file_table.c
--- linux-2.6.12-rc4/fs/file_table.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/file_table.c	2005-05-09 11:58:52.000000000 -0400
@@ -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 = {
@@ -123,6 +124,8 @@
 	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 -urN linux-2.6.12-rc4/fs/inode.c linux/fs/inode.c
--- linux-2.6.12-rc4/fs/inode.c	2005-05-09 11:52:48.000000000 -0400
+++ linux/fs/inode.c	2005-05-09 11:58:52.000000000 -0400
@@ -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:
@@ -128,6 +129,10 @@
 #ifdef CONFIG_QUOTA
 		memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));
 #endif
+#ifdef CONFIG_INOTIFY
+		INIT_LIST_HEAD(&inode->inotify_watches);
+		sema_init(&inode->inotify_sem, 1);
+#endif
 		inode->i_pipe = NULL;
 		inode->i_bdev = NULL;
 		inode->i_cdev = NULL;
@@ -346,6 +351,7 @@
 
 	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);
 
diff -urN linux-2.6.12-rc4/fs/inotify.c linux/fs/inotify.c
--- linux-2.6.12-rc4/fs/inotify.c	1969-12-31 19:00:00.000000000 -0500
+++ linux/fs/inotify.c	2005-05-09 11:58:52.000000000 -0400
@@ -0,0 +1,981 @@
+/*
+ * 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/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 int max_user_devices;
+static int max_user_watches;
+static unsigned int 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 intofiy 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 ssize_t show_max_queued_events(struct class_device *class, char *buf)
+{
+	return sprintf(buf, "%d\n", 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) {
+		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", 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) {
+		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", 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) {
+		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_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;
+
+	/* 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);
+}
+
+/*
+ * remove_kevent - cleans up and ultimately frees the given kevent
+ *
+ * Caller must hold dev->sem.
+ */
+static void remove_kevent(struct inotify_device *dev,
+			  struct inotify_kernel_event *kevent)
+{
+	list_del(&kevent->list);
+
+	dev->event_count--;
+	dev->queue_size -= sizeof(struct inotify_event) + kevent->event.len;
+
+	kfree(kevent->name);
+	kmem_cache_free(event_cachep, kevent);
+}
+
+/*
+ * inotify_dev_event_dequeue - destroy an event on the given device
+ *
+ * Caller must hold dev->sem.
+ */
+static void inotify_dev_event_dequeue(struct inotify_device *dev)
+{
+	if (!list_empty(&dev->events)) {
+		struct inotify_kernel_event *kevent;
+		kevent = inotify_dev_get_event(dev);
+		remove_kevent(dev, kevent);
+	}
+}
+
+/*
+ * inotify_dev_get_wd - returns the next WD for use by the given dev
+ *
+ * Callers must hold dev->sem.  This function can sleep.
+ */
+static int inotify_dev_get_wd(struct inotify_device *dev,
+			      struct inotify_watch *watch)
+{
+	int ret;
+
+	do {
+		if (unlikely(!idr_pre_get(&dev->idr, GFP_KERNEL)))
+			return -ENOSPC;
+		ret = idr_get_new(&dev->idr, watch, &watch->wd);
+	} while (ret == -EAGAIN);
+
+	return ret;
+}
+
+/*
+ * create_watch - creates a watch on the given device.
+ *
+ * Callers must hold dev->sem.  Calls inotify_dev_get_wd() so may sleep.
+ * Both 'dev' and 'inode' (by way of nameidata) need to be pinned.
+ */
+static struct inotify_watch *create_watch(struct inotify_device *dev,
+					  u32 mask, struct inode *inode)
+{
+	struct inotify_watch *watch;
+	int ret;
+
+	if (atomic_read(&dev->user->inotify_watches) >= max_user_watches)
+		return ERR_PTR(-ENOSPC);
+
+	watch = kmem_cache_alloc(watch_cachep, GFP_KERNEL);
+	if (unlikely(!watch))
+		return ERR_PTR(-ENOMEM);
+
+	ret = inotify_dev_get_wd(dev, watch);
+	if (unlikely(ret)) {
+		kmem_cache_free(watch_cachep, watch);
+		return ERR_PTR(ret);
+	}
+
+	watch->mask = mask;
+	atomic_set(&watch->count, 0);
+	INIT_LIST_HEAD(&watch->d_list);
+	INIT_LIST_HEAD(&watch->i_list);
+
+	/* save a reference to device and bump the count to make it official */
+	get_inotify_dev(dev);
+	watch->dev = dev;
+
+	/*
+	 * Save a reference to the inode and bump the ref count to make it
+	 * official.  We hold a reference to nameidata, which makes this safe.
+	 */
+	watch->inode = igrab(inode);
+
+	/* bump our own count, corresponding to our entry in dev->watches */
+	get_inotify_watch(watch);
+
+	atomic_inc(&dev->user->inotify_watches);
+
+	return watch;
+}
+
+/*
+ * inotify_find_dev - find the watch associated with the given inode and dev
+ *
+ * Callers must hold inode->inotify_sem.
+ */
+static struct inotify_watch *inode_find_dev(struct inode *inode,
+					    struct inotify_device *dev)
+{
+	struct inotify_watch *watch;
+
+	list_for_each_entry(watch, &inode->inotify_watches, i_list) {
+		if (watch->dev == dev)
+			return watch;
+	}
+
+	return NULL;
+}
+
+/*
+ * inotify_dev_is_watching_inode - is this device watching this inode?
+ *
+ * Callers must hold dev->sem.
+ */
+static inline 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;
+}
+
+/*
+ * remove_watch_no_event - remove_watch() without the IN_IGNORED event.
+ */
+static void remove_watch_no_event(struct inotify_watch *watch,
+				  struct inotify_device *dev)
+{
+	list_del(&watch->i_list);
+	list_del(&watch->d_list);
+
+	atomic_dec(&dev->user->inotify_watches);
+	idr_remove(&dev->idr, watch->wd);
+	put_inotify_watch(watch);
+}
+
+/*
+ * remove_watch - Remove a watch from both the device and the inode.  Sends
+ * the IN_IGNORED event to the given device signifying that the inode is no
+ * longer watched.
+ *
+ * Callers must hold both inode->inotify_sem and dev->sem.  We drop a
+ * reference to the inode before returning.
+ *
+ * The inode is not iput() so as to remain atomic.  If the inode needs to be
+ * iput(), the call returns one.  Otherwise, it returns zero.
+ */
+static void remove_watch(struct inotify_watch *watch,struct inotify_device *dev)
+{
+	inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL);
+	remove_watch_no_event(watch, dev);
+}
+
+/* Kernel API */
+
+/**
+ * inotify_inode_queue_event - queue an event to all watches on this inode
+ * @inode: inode event is originating from
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
+			       const char *name)
+{
+	struct inotify_watch *watch, *next;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		u32 watch_mask = watch->mask;
+		if (watch_mask & mask) {
+			struct inotify_device *dev = watch->dev;
+			get_inotify_watch(watch);
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, mask, cookie, name);
+			if (watch_mask & IN_ONESHOT)
+				remove_watch_no_event(watch, dev);
+			up(&dev->sem);
+			put_inotify_watch(watch);
+		}
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_queue_event);
+
+/**
+ * inotify_dentry_parent_queue_event - queue an event to a dentry's parent
+ * @dentry: the dentry in question, we queue against this dentry's parent
+ * @mask: event mask describing this event
+ * @cookie: cookie for synchronization, or zero
+ * @name: filename, if any
+ */
+void inotify_dentry_parent_queue_event(struct dentry *dentry, u32 mask,
+				       u32 cookie, const char *name)
+{
+	struct dentry *parent;
+	struct inode *inode;
+
+	spin_lock(&dentry->d_lock);
+	parent = dentry->d_parent;
+	inode = parent->d_inode;
+
+	/* We intentially do the list_empty() lockless.  The race is fine. */
+	if (!list_empty(&inode->inotify_watches)) {
+		dget(parent);
+		spin_unlock(&dentry->d_lock);
+		inotify_inode_queue_event(inode, mask, cookie, name);
+		dput(parent);
+	} else
+		spin_unlock(&dentry->d_lock);
+}
+EXPORT_SYMBOL_GPL(inotify_dentry_parent_queue_event);
+
+/**
+ * inotify_get_cookie - return a unique cookie for use in synchronizing events.
+ */
+u32 inotify_get_cookie(void)
+{
+	return atomic_inc_return(&inotify_cookie);
+}
+EXPORT_SYMBOL_GPL(inotify_get_cookie);
+
+/**
+ * inotify_unmount_inodes - an sb is unmounting.  handle any watched inodes.
+ * @list: list of inodes being unmounted (sb->s_inodes)
+ *
+ * Called with inode_lock held, protecting the unmounting super block's list
+ * of inodes, and with iprune_sem held, keeping shrink_icache_memory() at bay.
+ * We temporarily drop inode_lock, however, and CAN block.
+ */
+void inotify_unmount_inodes(struct list_head *list)
+{
+	struct inode *inode, *next_i;
+
+	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+		struct inotify_watch *watch, *next_w;
+		struct list_head *watches;
+
+		/* In case the remove_watch() drops a reference */
+		__iget(inode);
+
+		/*
+		 * We can safely drop inode_lock here because the per-sb list
+		 * of inodes must not change during unmount and iprune_sem
+		 * keeps shrink_icache_memory() away.
+		 */
+		spin_unlock(&inode_lock);
+
+		/* for each watch, send IN_UNMOUNT and then remove it */
+		down(&inode->inotify_sem);
+		watches = &inode->inotify_watches;
+		list_for_each_entry_safe(watch, next_w, watches, i_list) {
+			struct inotify_device *dev = watch->dev;
+			down(&dev->sem);
+			inotify_dev_queue_event(dev, watch, IN_UNMOUNT,0,NULL);
+			remove_watch(watch, dev);
+			up(&dev->sem);
+		}
+		up(&inode->inotify_sem);
+		iput(inode);		
+
+		spin_lock(&inode_lock);
+	}
+}
+EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
+
+/**
+ * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
+ * @inode: inode that is about to be removed
+ */
+void inotify_inode_is_dead(struct inode *inode)
+{
+	struct inotify_watch *watch, *next;
+
+	down(&inode->inotify_sem);
+	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
+		struct inotify_device *dev = watch->dev;
+		down(&dev->sem);
+		remove_watch(watch, dev);
+		up(&dev->sem);
+	}
+	up(&inode->inotify_sem);
+}
+EXPORT_SYMBOL_GPL(inotify_inode_is_dead);
+
+/* Device Interface */
+
+static unsigned int inotify_poll(struct file *file, poll_table *wait)
+{
+	struct inotify_device *dev = file->private_data;
+	int ret = 0;
+
+	poll_wait(file, &dev->wq, wait);
+	down(&dev->sem);
+	if (!list_empty(&dev->events))
+		ret = POLLIN | POLLRDNORM;
+	up(&dev->sem);
+
+	return ret;
+}
+
+static ssize_t inotify_read(struct file *file, char __user *buf,
+			    size_t count, loff_t *pos)
+{
+	size_t event_size = sizeof (struct inotify_event);
+	struct inotify_device *dev;
+	char __user *start;
+	int ret;
+	DEFINE_WAIT(wait);
+
+	start = buf;
+	dev = file->private_data;
+
+	while (1) {
+		int events;
+
+		prepare_to_wait(&dev->wq, &wait, TASK_INTERRUPTIBLE);
+
+		down(&dev->sem);
+		events = !list_empty(&dev->events);
+		up(&dev->sem);
+		if (events) {
+			ret = 0;
+			break;
+		}
+
+		if (file->f_flags & O_NONBLOCK) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			ret = -EINTR;
+			break;
+		}
+
+		schedule();
+	}
+
+	finish_wait(&dev->wq, &wait);
+	if (ret)
+		return ret;
+
+	down(&dev->sem);
+	while (1) {
+		struct inotify_kernel_event *kevent;
+
+		ret = buf - start;
+		if (list_empty(&dev->events))
+			break;
+
+		kevent = inotify_dev_get_event(dev);
+		if (event_size + kevent->event.len > count)
+			break;
+
+		if (copy_to_user(buf, &kevent->event, event_size)) {
+			ret = -EFAULT;
+			break;
+		}
+		buf += event_size;
+		count -= event_size;
+
+		if (kevent->name) {
+			if (copy_to_user(buf, kevent->name, kevent->event.len)){
+				ret = -EFAULT;
+				break;
+			}
+			buf += kevent->event.len;
+			count -= kevent->event.len;
+		}
+
+		remove_kevent(dev, kevent);
+	}
+	up(&dev->sem);
+
+	return ret;
+}
+
+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 (unlikely(atomic_read(&user->inotify_devs) >= max_user_devices)) {
+		ret = -EMFILE;
+		goto out_err;
+	}
+
+	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	if (unlikely(!dev)) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	idr_init(&dev->idr);
+	INIT_LIST_HEAD(&dev->events);
+	INIT_LIST_HEAD(&dev->watches);
+	init_waitqueue_head(&dev->wq);
+	sema_init(&dev->sem, 1);
+	dev->event_count = 0;
+	dev->queue_size = 0;
+	dev->max_events = max_queued_events;
+	dev->user = user;
+	atomic_set(&dev->count, 0);
+
+	get_inotify_dev(dev);
+	atomic_inc(&user->inotify_devs);
+
+	file->private_data = dev;
+
+	nonseekable_open(inode, file);
+
+	return 0;
+out_err:
+	free_uid(user);
+	return ret;
+}
+
+static int inotify_release(struct inode *ignored, struct file *file)
+{
+	struct inotify_device *dev = file->private_data;
+
+	/*
+	 * Destroy all of the watches on this device.  Unfortunately, not very
+	 * pretty.  We cannot do a simple iteration over the list, because we
+	 * do not know the inode until we iterate to the watch.  But we need to
+	 * hold inode->inotify_sem before dev->sem.  The following works.
+	 */
+	while (1) {
+		struct inotify_watch *watch;
+		struct list_head *watches;
+		struct inode *inode;
+
+		down(&dev->sem);
+		watches = &dev->watches;
+		if (list_empty(watches)) {
+			up(&dev->sem);
+			break;
+		}
+		watch = list_entry(watches->next, struct inotify_watch, d_list);
+		get_inotify_watch(watch);
+		up(&dev->sem);
+
+		inode = watch->inode;
+		down(&inode->inotify_sem);
+		down(&dev->sem);
+		remove_watch_no_event(watch, dev);
+		up(&dev->sem);
+		up(&inode->inotify_sem);
+		put_inotify_watch(watch);
+	}
+
+	/* destroy all of the events on this device */
+	down(&dev->sem);
+	while (!list_empty(&dev->events))
+		inotify_dev_event_dequeue(dev);
+	up(&dev->sem);
+
+	/* free this device: the put matching the get in inotify_open() */
+	put_inotify_dev(dev);
+
+	return 0;
+}
+
+static int inotify_add_watch(struct inotify_device *dev, int fd, u32 mask)
+{
+	struct inotify_watch *watch, *old;
+	struct inode *inode;
+	struct file *filp;
+	int ret;
+
+	filp = fget(fd);
+	if (!filp)
+		return -EBADF;
+	inode = filp->f_dentry->d_inode;
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* don't let user-space set invalid bits: we don't want flags set */
+	mask &= IN_ALL_EVENTS;
+	if (!mask) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Handle the case of re-adding a watch on an (inode,dev) pair that we
+	 * are already watching.  We just update the mask and return its wd.
+	 */
+	old = inode_find_dev(inode, dev);
+	if (unlikely(old)) {
+		old->mask = mask;
+		ret = old->wd;
+		goto out;
+	}
+
+	watch = create_watch(dev, mask, inode);
+	if (unlikely(IS_ERR(watch))) {
+		ret = PTR_ERR(watch);
+		goto out;
+	}
+
+	/* Add the watch to the device's and the inode's list */
+	list_add(&watch->d_list, &dev->watches);
+	list_add(&watch->i_list, &inode->inotify_watches);
+	ret = watch->wd;
+
+out:
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	fput(filp);
+
+	return ret;
+}
+
+/*
+ * inotify_ignore - handle the INOTIFY_IGNORE ioctl, asking that a given wd be
+ * removed from the device.
+ *
+ * Can sleep.
+ */
+static int inotify_ignore(struct inotify_device *dev, s32 wd)
+{
+	struct inotify_watch *watch;
+	struct inode *inode;
+
+	down(&dev->sem);
+	watch = idr_find(&dev->idr, wd);
+	if (unlikely(!watch)) {
+		up(&dev->sem);
+		return -EINVAL;
+	}
+	get_inotify_watch(watch);
+	inode = watch->inode;
+	up(&dev->sem);
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* make sure that we did not race */
+	watch = idr_find(&dev->idr, wd);
+	if (likely(watch))
+		remove_watch(watch, dev);
+
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	put_inotify_watch(watch);
+
+	return 0;
+}
+
+static long inotify_ioctl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	struct inotify_device *dev;
+	struct inotify_watch_request request;
+	void __user *p;
+	int ret = -ENOTTY;
+	s32 wd;
+
+	dev = file->private_data;
+	p = (void __user *) arg;
+
+	switch (cmd) {
+	case INOTIFY_WATCH:
+		if (unlikely(copy_from_user(&request, p, sizeof (request)))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_add_watch(dev, request.fd, request.mask);
+		break;
+	case INOTIFY_IGNORE:
+		if (unlikely(get_user(wd, (int __user *) p))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = inotify_ignore(dev, wd);
+		break;
+	case FIONREAD:
+		ret = put_user(dev->queue_size, (int __user *) p);
+		break;
+	}
+
+	return ret;
+}
+
+static struct file_operations inotify_fops = {
+	.owner		= THIS_MODULE,
+	.poll		= inotify_poll,
+	.read		= inotify_read,
+	.open		= inotify_open,
+	.release	= inotify_release,
+	.unlocked_ioctl	= inotify_ioctl,
+	.compat_ioctl	= inotify_ioctl,
+};
+
+static struct miscdevice inotify_device = {
+	.minor  = MISC_DYNAMIC_MINOR,
+	.name	= "inotify",
+	.fops	= &inotify_fops,
+};
+
+/*
+ * inotify_init - Our initialization function.  Note that we cannnot return
+ * error because we have compiled-in VFS hooks.  So an (unlikely) failure here
+ * must result in panic().
+ */
+static int __init inotify_init(void)
+{
+	struct class_device *class;
+	int ret;
+
+	ret = misc_register(&inotify_device);
+	if (unlikely(ret))
+		panic("inotify: misc_register returned %d\n", ret);
+
+	max_queued_events = 8192;
+	max_user_devices = 128;
+	max_user_watches = 8192;
+
+	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);
+
+	printk(KERN_INFO "inotify device minor=%d\n", inotify_device.minor);
+
+	return 0;
+}
+
+module_init(inotify_init);
diff -urN linux-2.6.12-rc4/fs/Kconfig linux/fs/Kconfig
--- linux-2.6.12-rc4/fs/Kconfig	2005-05-09 11:52:48.000000000 -0400
+++ linux/fs/Kconfig	2005-05-09 11:58:52.000000000 -0400
@@ -339,6 +339,19 @@
 	  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 -urN linux-2.6.12-rc4/fs/Makefile linux/fs/Makefile
--- linux-2.6.12-rc4/fs/Makefile	2005-03-02 02:38:10.000000000 -0500
+++ linux/fs/Makefile	2005-05-09 11:58:52.000000000 -0400
@@ -11,6 +11,7 @@
 		attr.o bad_inode.o file.o filesystems.o namespace.o aio.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \
 
+obj-$(CONFIG_INOTIFY)		+= inotify.o
 obj-$(CONFIG_EPOLL)		+= eventpoll.o
 obj-$(CONFIG_COMPAT)		+= compat.o
 
diff -urN linux-2.6.12-rc4/fs/namei.c linux/fs/namei.c
--- linux-2.6.12-rc4/fs/namei.c	2005-05-09 11:52:48.000000000 -0400
+++ linux/fs/namei.c	2005-05-09 11:58:52.000000000 -0400
@@ -21,7 +21,7 @@
 #include <linux/namei.h>
 #include <linux/quotaops.h>
 #include <linux/pagemap.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
 #include <linux/security.h>
@@ -1296,7 +1296,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;
@@ -1601,7 +1601,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;
@@ -1674,7 +1674,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;
@@ -1765,7 +1765,7 @@
 	}
 	up(&dentry->d_inode->i_sem);
 	if (!error) {
-		inode_dir_notify(dir, DN_DELETE);
+		fsnotify_rmdir(dentry, dentry->d_inode, dir);
 		d_delete(dentry);
 	}
 	dput(dentry);
@@ -1838,9 +1838,10 @@
 
 	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
 	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
+		fsnotify_unlink(dentry, dir);
 		d_delete(dentry);
-		inode_dir_notify(dir, DN_DELETE);
 	}
+
 	return error;
 }
 
@@ -1914,7 +1915,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;
@@ -1987,7 +1988,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;
@@ -2151,6 +2152,7 @@
 {
 	int error;
 	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	const char *old_name;
 
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
@@ -2172,18 +2174,18 @@
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
 
+	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
+
 	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, is_dir);
 	}
+	fsnotify_oldname_free(old_name);
+
 	return error;
 }
 
diff -urN linux-2.6.12-rc4/fs/open.c linux/fs/open.c
--- linux-2.6.12-rc4/fs/open.c	2005-03-02 02:37:47.000000000 -0500
+++ linux/fs/open.c	2005-05-09 11:58:52.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/smp_lock.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
@@ -944,9 +944,11 @@
 		fd = get_unused_fd();
 		if (fd >= 0) {
 			struct file *f = filp_open(tmp, flags, mode);
+
 			error = PTR_ERR(f);
 			if (IS_ERR(f))
 				goto out_error;
+			fsnotify_open(f->f_dentry);
 			fd_install(fd, f);
 		}
 out:
diff -urN linux-2.6.12-rc4/fs/read_write.c linux/fs/read_write.c
--- linux-2.6.12-rc4/fs/read_write.c	2005-05-09 11:52:48.000000000 -0400
+++ linux/fs/read_write.c	2005-05-09 11:58:52.000000000 -0400
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
-#include <linux/dnotify.h>
+#include <linux/fsnotify.h>
 #include <linux/security.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
@@ -239,7 +239,7 @@
 			else
 				ret = do_sync_read(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_ACCESS);
+				fsnotify_access(file->f_dentry);
 				current->rchar += ret;
 			}
 			current->syscr++;
@@ -287,7 +287,7 @@
 			else
 				ret = do_sync_write(file, buf, count, pos);
 			if (ret > 0) {
-				dnotify_parent(file->f_dentry, DN_MODIFY);
+				fsnotify_modify(file->f_dentry);
 				current->wchar += ret;
 			}
 			current->syscw++;
@@ -523,9 +523,12 @@
 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) {
+		if (type == READ)
+			fsnotify_access(file->f_dentry);
+		else
+			fsnotify_modify(file->f_dentry);
+	}
 	return ret;
 Efault:
 	ret = -EFAULT;
diff -urN linux-2.6.12-rc4/fs/xattr.c linux/fs/xattr.c
--- linux-2.6.12-rc4/fs/xattr.c	2005-03-02 02:38:07.000000000 -0500
+++ linux/fs/xattr.c	2005-05-09 11:58:52.000000000 -0400
@@ -16,6 +16,7 @@
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/module.h>
+#include <linux/fsnotify.h>
 #include <asm/uaccess.h>
 
 /*
@@ -57,8 +58,10 @@
 		if (error)
 			goto out;
 		error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
-		if (!error)
+		if (!error) {
+			fsnotify_xattr(d);
 			security_inode_post_setxattr(d, kname, kvalue, size, flags);
+		}
 out:
 		up(&d->d_inode->i_sem);
 	}
diff -urN linux-2.6.12-rc4/include/linux/fs.h linux/include/linux/fs.h
--- linux-2.6.12-rc4/include/linux/fs.h	2005-05-09 11:52:48.000000000 -0400
+++ linux/include/linux/fs.h	2005-05-09 11:58:52.000000000 -0400
@@ -471,6 +471,11 @@
 	struct dnotify_struct	*i_dnotify; /* for directory notifications */
 #endif
 
+#ifdef CONFIG_INOTIFY
+	struct list_head	inotify_watches; /* watches on this inode */
+	struct semaphore	inotify_sem;	/* protects the watches list */
+#endif
+
 	unsigned long		i_state;
 	unsigned long		dirtied_when;	/* jiffies of first dirtying */
 
@@ -1369,7 +1374,6 @@
 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 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.12-rc4/include/linux/fsnotify.h linux/include/linux/fsnotify.h
--- linux-2.6.12-rc4/include/linux/fsnotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/fsnotify.h	2005-05-09 11:58:52.000000000 -0400
@@ -0,0 +1,257 @@
+#ifndef _LINUX_FS_NOTIFY_H
+#define _LINUX_FS_NOTIFY_H
+
+/*
+ * include/linux/fsnotify.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 <linux/dnotify.h>
+#include <linux/inotify.h>
+
+/*
+ * 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,
+				 int isdir)
+{
+	u32 cookie = inotify_get_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);
+	}
+
+	if (isdir)
+		isdir = IN_ISDIR;
+	inotify_inode_queue_event(old_dir, IN_MOVED_FROM|isdir,cookie,old_name);
+	inotify_inode_queue_event(new_dir, IN_MOVED_TO|isdir, cookie, new_name);
+}
+
+/*
+ * fsnotify_unlink - file was unlinked
+ */
+static inline void fsnotify_unlink(struct dentry *dentry, struct inode *dir)
+{
+	struct inode *inode = dentry->d_inode;
+
+	inode_dir_notify(dir, DN_DELETE);
+	inotify_inode_queue_event(dir, IN_DELETE, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL);
+
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * 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|IN_ISDIR,0,dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF | IN_ISDIR, 0, NULL);
+	inotify_inode_is_dead(inode);
+}
+
+/*
+ * fsnotify_create - 'name' was linked in
+ */
+static inline void fsnotify_create(struct inode *inode, const char *name)
+{
+	inode_dir_notify(inode, DN_CREATE);
+	inotify_inode_queue_event(inode, IN_CREATE, 0, name);
+}
+
+/*
+ * 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 | IN_ISDIR, 0, name);
+}
+
+/*
+ * fsnotify_access - file was read
+ */
+static inline void fsnotify_access(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ACCESS;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_ACCESS);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_modify - file was modified
+ */
+static inline void fsnotify_modify(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_MODIFY;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	dnotify_parent(dentry, DN_MODIFY);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_open - file was opened
+ */
+static inline void fsnotify_open(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_OPEN;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+}
+
+/*
+ * fsnotify_close - file was closed
+ */
+static inline void fsnotify_close(struct file *file)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+	mode_t mode = file->f_mode;
+	u32 mask = (mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
+}
+
+/*
+ * fsnotify_xattr - extended attributes were changed
+ */
+static inline void fsnotify_xattr(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	u32 mask = IN_ATTRIB;
+
+	if (S_ISDIR(inode->i_mode))
+		mask |= IN_ISDIR;
+
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	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)
+{
+	struct inode *inode = dentry->d_inode;
+	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) {
+		if (S_ISDIR(inode->i_mode))
+			in_mask |= IN_ISDIR;
+		inotify_inode_queue_event(inode, in_mask, 0, NULL);
+		inotify_dentry_parent_queue_event(dentry, in_mask, 0,
+						  dentry->d_name.name);
+	}
+}
+
+#ifdef CONFIG_INOTIFY	/* inotify helpers */
+
+/*
+ * fsnotify_oldname_init - save off the old filename before we change it
+ *
+ * XXX: This could be kstrdup if only we could add that to lib/string.c
+ */
+static inline char *fsnotify_oldname_init(const char *name)
+{
+	size_t len;
+	char *buf;
+
+	len = strlen(name) + 1;
+	buf = kmalloc(len, GFP_KERNEL);
+	if (likely(buf))
+		memcpy(buf, name, len);
+	return buf;
+}
+
+/*
+ * 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(const char *name)
+{
+	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.12-rc4/include/linux/inotify.h linux/include/linux/inotify.h
--- linux-2.6.12-rc4/include/linux/inotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/inotify.h	2005-05-09 11:58:52.000000000 -0400
@@ -0,0 +1,124 @@
+/*
+ * Inode based directory notification for Linux
+ *
+ * Copyright (C) 2005 John McCutchan
+ */
+
+#ifndef _LINUX_INOTIFY_H
+#define _LINUX_INOTIFY_H
+
+#include <linux/types.h>
+
+/*
+ * 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 to synchronize two events */
+	__u32		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 {
+	int		fd;		/* fd of filename to watch */
+	__u32		mask;		/* event mask */
+};
+
+/* the following are legal, implemented events that user-space can watch for */
+#define IN_ACCESS		0x00000001	/* File was accessed */
+#define IN_MODIFY		0x00000002	/* File was modified */
+#define IN_ATTRIB		0x00000004	/* Metadata changed */
+#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_CREATE		0x00000100	/* Subfile was created */
+#define IN_DELETE		0x00000200	/* Subfile was deleted */
+#define IN_DELETE_SELF		0x00000400	/* Self was deleted */
+
+/* the following are legal events.  they are sent as needed to any watch */
+#define IN_UNMOUNT		0x00002000	/* Backing fs was unmounted */
+#define IN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
+#define IN_IGNORED		0x00008000	/* File was ignored */
+
+/* helper events */
+#define IN_CLOSE		(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */
+#define IN_MOVE			(IN_MOVED_FROM | IN_MOVED_TO) /* moves */
+
+/* special flags */
+#define IN_ISDIR		0x40000000	/* event occurred against dir */
+#define IN_ONESHOT		0x80000000	/* only send event once */
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define IN_ALL_EVENTS	(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
+			 IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \
+			 IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF)
+
+#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 <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/config.h>
+
+#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_unmount_inodes(struct list_head *);
+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_unmount_inodes(struct list_head *list)
+{
+}
+
+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.12-rc4/include/linux/sched.h linux/include/linux/sched.h
--- linux-2.6.12-rc4/include/linux/sched.h	2005-05-09 11:52:49.000000000 -0400
+++ linux/include/linux/sched.h	2005-05-09 11:58:52.000000000 -0400
@@ -404,6 +404,10 @@
 	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? */
+#ifdef CONFIG_INOTIFY
+	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? */
+#endif
 	/* 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.12-rc4/kernel/user.c linux/kernel/user.c
--- linux-2.6.12-rc4/kernel/user.c	2005-05-09 11:52:49.000000000 -0400
+++ linux/kernel/user.c	2005-05-09 11:58:52.000000000 -0400
@@ -120,6 +120,10 @@
 		atomic_set(&new->processes, 0);
 		atomic_set(&new->files, 0);
 		atomic_set(&new->sigpending, 0);
+#ifdef CONFIG_INOTIFY
+		atomic_set(&new->inotify_watches, 0);
+		atomic_set(&new->inotify_devs, 0);
+#endif
 
 		new->mq_bytes = 0;
 		new->locked_shm = 0;



^ permalink raw reply	[flat|nested] 45+ messages in thread

* [patch] inotify.
@ 2005-01-06 20:00 Robert Love
  0 siblings, 0 replies; 45+ messages in thread
From: Robert Love @ 2005-01-06 20:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: akpm, ttb

Andrew,

Below is a patch adding inotify to 2.6.10-mm2.

inotify is intended to correct the deficiencies of dnotify:

        * 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.

The primary change since the last post is a reworking of the locking and
refcounting.  A bit more such reworking is expected.

Inotify is currently used by Beagle (a desktop search infrastructure)
and Gamin (a FAM replacement).

Best,

        Robert Love


inotify.  get with it.
Signed-off-by: Robert Love <rml@novell.com>

 drivers/char/Kconfig       |   13 
 drivers/char/Makefile      |    2 
 drivers/char/inotify.c     | 1024 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/char/misc.c        |   14 
 fs/attr.c                  |   73 ++-
 fs/file_table.c            |    7 
 fs/inode.c                 |    3 
 fs/namei.c                 |   35 +
 fs/open.c                  |    5 
 fs/read_write.c            |   27 -
 fs/super.c                 |    2 
 include/linux/fs.h         |    7 
 include/linux/inotify.h    |  155 ++++++
 include/linux/miscdevice.h |    5 
 include/linux/sched.h      |    2 
 kernel/user.c              |    2 
 16 files changed, 1339 insertions(+), 37 deletions(-)

diff -urN linux-2.6.10-mm2/drivers/char/inotify.c linux/drivers/char/inotify.c
--- linux-2.6.10-mm2/drivers/char/inotify.c	1969-12-31 19:00:00.000000000 -0500
+++ linux/drivers/char/inotify.c	2005-01-06 14:47:51.391599744 -0500
@@ -0,0 +1,1024 @@
+/*
+ * Inode based directory notifications for Linux.
+ *
+ * Copyright (C) 2004 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/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 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		event_count;
+	unsigned int		max_events;
+	struct user_struct	*user;
+};
+
+struct inotify_watch {
+	s32 			wd;	/* watch descriptor */
+	u32			mask;
+	struct inode		*inode;
+	struct inotify_device	*dev;
+	struct list_head	d_list;	/* device list */
+	struct list_head	i_list; /* inode list */
+};
+
+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);
+
+/*
+ * A list of these is attached to each instance of the driver
+ * when the drivers read() gets called, this list is walked and
+ * all events that can fit in the buffer get delivered
+ */
+struct inotify_kernel_event {
+        struct list_head        list;
+	struct inotify_event	event;
+};
+
+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 iget() it
+ */
+static struct inode * find_inode(const char __user *dirname)
+{
+	struct inode *inode;
+	struct nameidata nd;
+	int error;
+
+	error = __user_walk(dirname, LOOKUP_FOLLOW, &nd);
+	if (error)
+		return ERR_PTR(error);
+
+	inode = nd.dentry->d_inode;
+
+	/* you can only watch an inode if you have read permissions on it */
+	error = permission(inode, MAY_READ, NULL);
+	if (error) {
+		inode = ERR_PTR(error);
+		goto release_and_out;
+	}
+
+	spin_lock(&inode_lock);
+	__iget(inode);
+	spin_unlock(&inode_lock);
+release_and_out:
+	path_release(&nd);
+	return inode;
+}
+
+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 out just in case */
+	memset(kevent, 0, sizeof(struct inotify_kernel_event));
+
+	kevent->event.wd = wd;
+	kevent->event.mask = mask;
+	kevent->event.cookie = cookie;
+	INIT_LIST_HEAD(&kevent->list);
+
+	if (filename) {
+		strncpy(kevent->event.filename, filename, INOTIFY_FILENAME_MAX);
+		kevent->event.filename[INOTIFY_FILENAME_MAX-1] = '\0';
+	} else
+		kevent->event.filename[0] = '\0';
+
+	return kevent;
+}
+
+void delete_kernel_event(struct inotify_kernel_event *kevent)
+{
+	if (!kevent)
+		return;
+	kmem_cache_free(event_cachep, 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))
+
+/* Does this events mask get sent to the watch ? */
+#define event_and(event_mask,watches_mask) 	((event_mask == IN_UNMOUNT) || \
+						(event_mask == IN_IGNORED) || \
+						(event_mask & watches_mask))
+
+/*
+ * 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;
+
+	/* Check if the new event is a duplicate of the last event queued. */
+	last = inotify_dev_get_event(dev);
+	if (dev->event_count && last->event.mask == mask &&
+			last->event.wd == watch->wd) {
+		/* Check if the filenames match */
+		if (!filename && last->event.filename[0] == '\0')
+			return;
+		if (filename && !strcmp(last->event.filename, 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;
+	}
+
+	if (!event_and(mask, watch->inode->inotify_data->watch_mask) ||
+			!event_and(mask, watch->mask))
+		return;
+
+	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++;	
+	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);
+	dev->event_count--;
+	delete_kernel_event(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_update_watch_mask - update inode->inotify_data->watch_mask
+ *
+ * Grabs inode->inotify_data->lock.
+ */
+static void inode_update_watch_mask(struct inode *inode)
+{
+	struct inotify_inode_data *data;
+	struct inotify_watch *watch;
+	u32 new_mask;
+
+	data = get_inode_data(inode);
+	if (!data)	/* FIXME: this should never happen */
+		return;
+	spin_lock(&data->lock);
+
+	new_mask = 0;
+	list_for_each_entry(watch, &data->watches, i_list)
+		new_mask |= watch->mask;
+
+	data->watch_mask = new_mask;
+
+	spin_unlock(&data->lock);
+	put_inode_data(inode);
+}
+
+/*
+ * 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);
+		inode->inotify_data->watch_mask = 0;
+		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);
+	inode_update_watch_mask(inode);
+
+	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);
+	inode_update_watch_mask(inode);
+
+	/* 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 *filename)
+{
+	struct inotify_watch *watch;
+
+	if (!inode->inotify_data)
+		return;
+
+	list_for_each_entry(watch, &inode->inotify_data->watches, i_list) {
+		spin_lock(&watch->dev->lock);
+		inotify_dev_queue_event(watch->dev, watch, mask, cookie,
+					filename);
+		spin_unlock(&watch->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;
+
+	parent = dget_parent(dentry);
+	inotify_inode_queue_event(parent->d_inode, mask, cookie, filename);
+	dput(parent);
+}
+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);
+
+/*
+ * watch->inode must be pinned.  We drop a reference before returning.
+ */
+static void ignore_helper(struct inotify_watch *watch, int event)
+{
+	struct inotify_device *dev;
+	struct inode *inode;
+
+	inode = watch->inode;
+	dev = watch->dev;
+
+	spin_lock(&dev->lock);
+
+	if (event)
+		inotify_dev_queue_event(dev, watch, event, 0, NULL);
+
+	inode_rm_watch(inode, watch);
+	inotify_dev_rm_watch(watch->dev, watch);
+
+	delete_watch(dev, watch);
+	spin_unlock(&dev->lock);
+
+	iput(inode);
+}
+
+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)
+			ignore_helper(watch, IN_UNMOUNT);
+		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)
+		ignore_helper(watch, 0);
+	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 -EINVAL;
+
+	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 -ERESTARTSYS;
+
+		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);
+		if (copy_to_user(buf, &kevent->event, event_size))
+			return -EFAULT;
+
+		spin_lock(&dev->lock);
+		inotify_dev_event_dequeue(dev);
+		spin_unlock(&dev->lock);
+		count -= event_size;
+		buf += event_size;
+	}
+
+	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 = -ENOSPC;
+		goto out_err;
+	}
+
+	atomic_inc(&current->user->inotify_devs);
+
+	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	idr_init(&dev->idr);
+
+	INIT_LIST_HEAD(&dev->events);
+	INIT_LIST_HEAD(&dev->watches);
+	init_waitqueue_head(&dev->wait);
+
+	dev->event_count = 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: Do we want a lock 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)
+		ignore_helper(watch, 0);
+}
+
+/*
+ * 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;
+	int ret;
+
+	inode = find_inode((const char __user*) request->dirname);
+	if (IS_ERR(inode))
+		return PTR_ERR(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;
+		inode_update_watch_mask(inode);
+		spin_unlock(&dev->lock);
+		iput(inode);
+
+		return owatch->wd;
+	}
+
+	spin_unlock(&dev->lock);
+
+	watch = create_watch(dev, request->mask, inode);
+	if (!watch) {
+		iput(inode);
+		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);
+		iput(inode);
+		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);
+		iput(inode);
+		return ret;
+	}
+
+	spin_unlock(&dev->lock);
+
+	return watch->wd;
+}
+
+static int inotify_ignore(struct inotify_device *dev, s32 wd)
+{
+	struct inotify_watch *watch;
+
+	/*
+	 * FIXME: Silly to grab dev->lock here and then drop it, when
+	 * ignore_helper() grabs it anyway a few lines down.
+	 */
+	spin_lock(&dev->lock);
+	watch = dev_find_wd(dev, wd);
+	spin_unlock(&dev->lock);
+	if (!watch)
+		return -EINVAL;
+	ignore_helper(watch, 0);
+
+	return 0;
+}
+
+/*
+ * 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;
+	int bytes;
+	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:
+		bytes = dev->event_count * sizeof(struct inotify_event);
+		return put_user(bytes, (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,
+};
+
+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.10-mm2/drivers/char/Kconfig linux/drivers/char/Kconfig
--- linux-2.6.10-mm2/drivers/char/Kconfig	2005-01-06 14:47:13.045429248 -0500
+++ linux/drivers/char/Kconfig	2005-01-06 14:47:51.393599440 -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.10-mm2/drivers/char/Makefile linux/drivers/char/Makefile
--- linux-2.6.10-mm2/drivers/char/Makefile	2005-01-06 14:45:48.809235096 -0500
+++ linux/drivers/char/Makefile	2005-01-06 14:47:51.394599288 -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.10-mm2/drivers/char/misc.c linux/drivers/char/misc.c
--- linux-2.6.10-mm2/drivers/char/misc.c	2005-01-06 14:45:48.808235248 -0500
+++ linux/drivers/char/misc.c	2005-01-06 14:47:51.395599136 -0500
@@ -207,10 +207,9 @@
 int misc_register(struct miscdevice * misc)
 {
 	struct miscdevice *c;
-	struct class_device *class;
 	dev_t dev;
 	int err;
-	
+
 	down(&misc_sem);
 	list_for_each_entry(c, &misc_list, list) {
 		if (c->minor == misc->minor) {
@@ -224,8 +223,7 @@
 		while (--i >= 0)
 			if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
 				break;
-		if (i<0)
-		{
+		if (i<0) {
 			up(&misc_sem);
 			return -EBUSY;
 		}
@@ -240,10 +238,10 @@
 	}
 	dev = MKDEV(MISC_MAJOR, misc->minor);
 
-	class = class_simple_device_add(misc_class, dev,
-					misc->dev, misc->name);
-	if (IS_ERR(class)) {
-		err = PTR_ERR(class);
+	misc->class = class_simple_device_add(misc_class, dev,
+					      misc->dev, misc->name);
+	if (IS_ERR(misc->class)) {
+		err = PTR_ERR(misc->class);
 		goto out;
 	}
 
diff -urN linux-2.6.10-mm2/fs/attr.c linux/fs/attr.c
--- linux-2.6.10-mm2/fs/attr.c	2005-01-06 14:47:14.737172064 -0500
+++ linux/fs/attr.c	2005-01-06 14:47:51.396598984 -0500
@@ -11,6 +11,7 @@
 #include <linux/string.h>
 #include <linux/smp_lock.h>
 #include <linux/dnotify.h>
+#include <linux/inotify.h>
 #include <linux/fcntl.h>
 #include <linux/quotaops.h>
 #include <linux/security.h>
@@ -107,29 +108,51 @@
 out:
 	return error;
 }
-
 EXPORT_SYMBOL(inode_setattr);
 
-int setattr_mask(unsigned int ia_valid)
+void setattr_mask (unsigned int ia_valid, int *dn_mask, u32 *in_mask)
 {
-	unsigned long dn_mask = 0;
+	int dnmask;
+	u32 inmask;
 
-	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;
+	inmask = 0;
+	dnmask = 0;
+
+	if (!dn_mask || !in_mask) {
+		return;
+	}
+        if (ia_valid & ATTR_UID) {
+                inmask |= IN_ATTRIB;
+		dnmask |= DN_ATTRIB;
+	}
+        if (ia_valid & ATTR_GID) {
+                inmask |= IN_ATTRIB;
+		dnmask |= DN_ATTRIB;
+	}
+        if (ia_valid & ATTR_SIZE) {
+                inmask |= IN_MODIFY;
+		dnmask |= DN_MODIFY;
+	}
+        /* both times implies a utime(s) call */
+        if ((ia_valid & (ATTR_ATIME|ATTR_MTIME)) == (ATTR_ATIME|ATTR_MTIME)) {
+                inmask |= IN_ATTRIB;
+		dnmask |= DN_ATTRIB;
+	}
+        else if (ia_valid & ATTR_ATIME) {
+                inmask |= IN_ACCESS;
+		dnmask |= DN_ACCESS;
+	}
+        else if (ia_valid & ATTR_MTIME) {
+                inmask |= IN_MODIFY;
+		dnmask |= DN_MODIFY;
+	}
+        if (ia_valid & ATTR_MODE) {
+                inmask |= IN_ATTRIB;
+		dnmask |= DN_ATTRIB;
+	}
+
+	*in_mask = inmask;
+	*dn_mask = dnmask;
 }
 
 int notify_change(struct dentry * dentry, struct iattr * attr)
@@ -188,9 +211,19 @@
 		}
 	}
 	if (!error) {
-		unsigned long dn_mask = setattr_mask(ia_valid);
+		int dn_mask;
+		u32 in_mask;
+
+		setattr_mask (ia_valid, &dn_mask, &in_mask);
+
 		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);
+		}
 	}
 	return error;
 }
diff -urN linux-2.6.10-mm2/fs/file_table.c linux/fs/file_table.c
--- linux-2.6.10-mm2/fs/file_table.c	2005-01-06 14:47:14.821159296 -0500
+++ linux/fs/file_table.c	2005-01-06 14:47:51.397598832 -0500
@@ -16,6 +16,7 @@
 #include <linux/eventpoll.h>
 #include <linux/mount.h>
 #include <linux/cdev.h>
+#include <linux/inotify.h>
 
 /* sysctl tunables... */
 struct files_stat_struct files_stat = {
@@ -120,6 +121,12 @@
 	struct dentry *dentry = file->f_dentry;
 	struct vfsmount *mnt = file->f_vfsmnt;
 	struct inode *inode = dentry->d_inode;
+	u32 mask;
+
+
+	mask = (file->f_mode & FMODE_WRITE) ? IN_CLOSE_WRITE : IN_CLOSE_NOWRITE;
+	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, mask, 0, NULL);
 
 	might_sleep();
 	/*
diff -urN linux-2.6.10-mm2/fs/inode.c linux/fs/inode.c
--- linux-2.6.10-mm2/fs/inode.c	2005-01-06 14:47:14.834157320 -0500
+++ linux/fs/inode.c	2005-01-06 14:47:51.399598528 -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.10-mm2/fs/namei.c linux/fs/namei.c
--- linux-2.6.10-mm2/fs/namei.c	2005-01-06 14:47:14.970136648 -0500
+++ linux/fs/namei.c	2005-01-06 14:47:51.402598072 -0500
@@ -22,6 +22,7 @@
 #include <linux/quotaops.h>
 #include <linux/pagemap.h>
 #include <linux/dnotify.h>
+#include <linux/inotify.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
 #include <linux/security.h>
@@ -1243,6 +1244,8 @@
 	error = dir->i_op->create(dir, dentry, mode, nd);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
+		inotify_inode_queue_event(dir, IN_CREATE_FILE,
+				0, dentry->d_name.name);
 		security_inode_post_create(dir, dentry, mode);
 	}
 	return error;
@@ -1557,6 +1560,8 @@
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
+		inotify_inode_queue_event(dir, IN_CREATE_FILE, 0,
+				dentry->d_name.name);
 		security_inode_post_mknod(dir, dentry, mode, dev);
 	}
 	return error;
@@ -1630,6 +1635,8 @@
 	error = dir->i_op->mkdir(dir, dentry, mode);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
+		inotify_inode_queue_event(dir, IN_CREATE_SUBDIR, 0,
+				dentry->d_name.name);
 		security_inode_post_mkdir(dir,dentry, mode);
 	}
 	return error;
@@ -1725,6 +1732,11 @@
 	up(&dentry->d_inode->i_sem);
 	if (!error) {
 		inode_dir_notify(dir, DN_DELETE);
+		inotify_inode_queue_event(dir, IN_DELETE_SUBDIR, 0,
+				dentry->d_name.name);
+		inotify_inode_queue_event(dentry->d_inode, IN_DELETE_SELF, 0,
+				NULL);
+		inotify_inode_is_dead (dentry->d_inode);
 		d_delete(dentry);
 	}
 	dput(dentry);
@@ -1797,8 +1809,13 @@
 
 	/* 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);
+		inotify_inode_queue_event(dir, IN_DELETE_FILE, 0,
+				dentry->d_name.name);
+		inotify_inode_queue_event(dentry->d_inode, IN_DELETE_SELF, 0,
+				NULL);
+		inotify_inode_is_dead (dentry->d_inode);
+		d_delete(dentry);
 	}
 	return error;
 }
@@ -1874,6 +1891,8 @@
 	error = dir->i_op->symlink(dir, dentry, oldname);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
+		inotify_inode_queue_event(dir, IN_CREATE_FILE, 0,
+				dentry->d_name.name);
 		security_inode_post_symlink(dir, dentry, oldname);
 	}
 	return error;
@@ -1947,6 +1966,8 @@
 	up(&old_dentry->d_inode->i_sem);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
+		inotify_inode_queue_event(dir, IN_CREATE_FILE, 0, 
+					new_dentry->d_name.name);
 		security_inode_post_link(old_dentry, dir, new_dentry);
 	}
 	return error;
@@ -2110,6 +2131,8 @@
 {
 	int error;
 	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	char *old_name;
+	u32 cookie;
 
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
@@ -2131,6 +2154,8 @@
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
 
+	old_name = inotify_oldname_init(old_dentry);
+
 	if (is_dir)
 		error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry);
 	else
@@ -2142,7 +2167,15 @@
 			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_dentry->d_name.name);
 	}
+	inotify_oldname_free(old_name);
+
 	return error;
 }
 
diff -urN linux-2.6.10-mm2/fs/open.c linux/fs/open.c
--- linux-2.6.10-mm2/fs/open.c	2005-01-06 14:45:47.599419016 -0500
+++ linux/fs/open.c	2005-01-06 14:47:51.403597920 -0500
@@ -11,6 +11,7 @@
 #include <linux/smp_lock.h>
 #include <linux/quotaops.h>
 #include <linux/dnotify.h>
+#include <linux/inotify.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
@@ -956,6 +957,10 @@
 			error = PTR_ERR(f);
 			if (IS_ERR(f))
 				goto out_error;
+			inotify_inode_queue_event(f->f_dentry->d_inode,
+					IN_OPEN, 0, NULL);
+			inotify_dentry_parent_queue_event(f->f_dentry, IN_OPEN,
+					0, f->f_dentry->d_name.name);
 			fd_install(fd, f);
 		}
 out:
diff -urN linux-2.6.10-mm2/fs/read_write.c linux/fs/read_write.c
--- linux-2.6.10-mm2/fs/read_write.c	2005-01-06 14:47:15.037126464 -0500
+++ linux/fs/read_write.c	2005-01-06 14:51:23.408368296 -0500
@@ -11,6 +11,7 @@
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
 #include <linux/dnotify.h>
+#include <linux/inotify.h>
 #include <linux/security.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
@@ -217,7 +218,12 @@
 			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;
+				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);
 				current->rchar += ret;
 			}
 			current->syscr++;
@@ -264,7 +270,12 @@
 			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;
+				dnotify_parent(dentry, DN_MODIFY);
+				inotify_dentry_parent_queue_event(dentry,
+					IN_MODIFY, 0, dentry->d_name.name);
+				inotify_inode_queue_event(inode, IN_MODIFY, 0,
+					NULL);
 				current->wchar += ret;
 			}
 			current->syscw++;
@@ -499,9 +510,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;
+		dnotify_parent(dentry, (type == READ) ? DN_ACCESS : DN_MODIFY);
+		inotify_dentry_parent_queue_event(dentry,
+				(type == READ) ? IN_ACCESS : IN_MODIFY, 0,
+				dentry->d_name.name);
+		inotify_inode_queue_event (dentry->d_inode,
+				(type == READ) ? IN_ACCESS : IN_MODIFY, 0, NULL);
+	}
 	return ret;
 }
 
diff -urN linux-2.6.10-mm2/fs/super.c linux/fs/super.c
--- linux-2.6.10-mm2/fs/super.c	2005-01-06 14:47:15.407070224 -0500
+++ linux/fs/super.c	2005-01-06 14:47:51.405597616 -0500
@@ -38,6 +38,7 @@
 #include <linux/idr.h>
 #include <linux/kobject.h>
 #include <asm/uaccess.h>
+#include <linux/inotify.h>
 
 
 void get_filesystem(struct file_system_type *fs);
@@ -229,6 +230,7 @@
 
 	if (root) {
 		sb->s_root = NULL;
+		inotify_super_block_umount(sb);
 		shrink_dcache_parent(root);
 		shrink_dcache_anon(&sb->s_anon);
 		dput(root);
diff -urN linux-2.6.10-mm2/include/linux/fs.h linux/include/linux/fs.h
--- linux-2.6.10-mm2/include/linux/fs.h	2005-01-06 14:47:19.491449304 -0500
+++ linux/include/linux/fs.h	2005-01-06 14:47:51.407597312 -0500
@@ -27,6 +27,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
@@ -476,6 +477,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 */
 
@@ -1366,7 +1371,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.10-mm2/include/linux/inotify.h linux/include/linux/inotify.h
--- linux-2.6.10-mm2/include/linux/inotify.h	1969-12-31 19:00:00.000000000 -0500
+++ linux/include/linux/inotify.h	2005-01-06 14:47:51.408597160 -0500
@@ -0,0 +1,155 @@
+/*
+ * Inode based directory notification for Linux
+ *
+ * Copyright (C) 2004 John McCutchan
+ */
+
+#ifndef _LINUX_INOTIFY_H
+#define _LINUX_INOTIFY_H
+
+#include <linux/types.h>
+#include <linux/limits.h>
+
+/* this size could limit things, since technically we could need PATH_MAX */
+#define INOTIFY_FILENAME_MAX	256
+
+/*
+ * 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, ...
+ *
+ * Note: When reading from the device you must provide a buffer that is a
+ * multiple of sizeof(struct inotify_event)
+ */
+struct inotify_event {
+	__s32 wd;
+	__u32 mask;
+	__u32 cookie;
+	char filename[INOTIFY_FILENAME_MAX];
+};
+
+/*
+ * struct inotify_watch_request - represents a watch request
+ *
+ * Pass to the inotify device via the INOTIFY_WATCH ioctl
+ */
+struct inotify_watch_request {
+	char *dirname;		/* 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 <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/config.h>
+
+struct inotify_inode_data {
+	struct list_head watches;
+	__u32 watch_mask;
+	spinlock_t lock;
+	atomic_t 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);
+extern __u32 setattr_mask_inotify(unsigned int);
+
+/* this could be kstrdup if only we could add that to lib/string.c */
+static inline char * inotify_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;
+}
+
+static inline void inotify_oldname_free(const char *old_name)
+{
+	kfree(old_name);
+}
+
+#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 char * inotify_oldname_init(struct dentry *old_dentry)
+{
+	return NULL;
+}
+
+static inline __u32 inotify_get_cookie(void)
+{
+	return 0;
+}
+
+static inline void inotify_oldname_free(const char *old_name)
+{
+}
+
+static inline int setattr_mask_inotify(unsigned int ia_mask)
+{
+	return 0;
+}
+
+#endif	/* CONFIG_INOTIFY */
+
+#endif	/* __KERNEL __ */
+
+#endif	/* _LINUX_INOTIFY_H */
diff -urN linux-2.6.10-mm2/include/linux/miscdevice.h linux/include/linux/miscdevice.h
--- linux-2.6.10-mm2/include/linux/miscdevice.h	2005-01-06 14:45:47.968362928 -0500
+++ linux/include/linux/miscdevice.h	2005-01-06 14:47:51.409597008 -0500
@@ -2,6 +2,7 @@
 #define _LINUX_MISCDEVICE_H
 #include <linux/module.h>
 #include <linux/major.h>
+#include <linux/device.h>
 
 #define PSMOUSE_MINOR  1
 #define MS_BUSMOUSE_MINOR 2
@@ -32,13 +33,13 @@
 
 struct device;
 
-struct miscdevice 
-{
+struct miscdevice  {
 	int minor;
 	const char *name;
 	struct file_operations *fops;
 	struct list_head list;
 	struct device *dev;
+	struct class_device *class;
 	char devfs_name[64];
 };
 
diff -urN linux-2.6.10-mm2/include/linux/sched.h linux/include/linux/sched.h
--- linux-2.6.10-mm2/include/linux/sched.h	2005-01-06 14:47:19.716415104 -0500
+++ linux/include/linux/sched.h	2005-01-06 14:47:51.410596856 -0500
@@ -366,6 +366,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.10-mm2/kernel/user.c linux/kernel/user.c
--- linux-2.6.10-mm2/kernel/user.c	2005-01-06 14:47:19.939381208 -0500
+++ linux/kernel/user.c	2005-01-06 14:47:51.410596856 -0500
@@ -119,6 +119,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;



^ permalink raw reply	[flat|nested] 45+ messages in thread

end of thread, other threads:[~2005-07-14  1:11 UTC | newest]

Thread overview: 45+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-06-15 17:18 [patch] inotify Robert Love
2005-06-16 17:52 ` Zach Brown
2005-06-16 18:25   ` Robert Love
2005-06-17  1:30     ` Nick Piggin
2005-06-17  1:35       ` Robert Love
2005-06-17 15:15         ` [patch] inotify, improved Robert Love
2005-06-17 15:37           ` Chris Friesen
2005-06-17 15:44             ` Robert Love
2005-06-17 16:11               ` Valdis.Kletnieks
2005-06-17 16:29                 ` Robert Love
2005-06-17 16:36                 ` Chris Friesen
2005-06-17 16:43                   ` Chris Wright
2005-06-17 16:46                   ` Muli Ben-Yehuda
2005-06-17 16:40               ` Chris Friesen
2005-06-17 17:57                 ` John McCutchan
2005-06-17 17:20           ` Zach Brown
2005-06-17 17:54             ` John McCutchan
2005-06-17 17:56               ` Zach Brown
2005-06-17 18:15                 ` John McCutchan
2005-06-17 18:17                   ` Zach Brown
2005-06-17 17:07     ` [patch] inotify Arnd Bergmann
2005-06-17 17:54       ` Christoph Hellwig
2005-06-17 18:12         ` John McCutchan
2005-06-17 18:16         ` Robert Love
2005-06-17 18:28           ` Christoph Hellwig
2005-06-17 18:38             ` Robert Love
2005-06-17 18:45               ` Christoph Hellwig
2005-06-17 18:54                 ` Robert Love
2005-06-17 17:56       ` John McCutchan
2005-06-17 21:33         ` Andrew Morton
2005-06-17 21:40           ` Robert Love
2005-06-17 23:52             ` Robert Love
2005-06-21  0:51               ` Neil Brown
2005-06-21  2:15                 ` John McCutchan
2005-06-21  2:29                   ` Neil Brown
2005-06-21  2:43                     ` John McCutchan
2005-06-21 15:55                     ` Robert Love
2005-07-14  0:25                       ` Neil Brown
2005-07-14  4:11                         ` John McCutchan
2005-06-18  0:05             ` Arnd Bergmann
2005-06-18  0:57               ` Robert Love
2005-06-18  1:51       ` Chris Wedgwood
  -- strict thread matches above, loose matches on Subject: below --
2005-05-09 16:05 Robert Love
2005-05-09 17:43 ` Coywolf Qi Hunt
2005-01-06 20:00 Robert Love

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).