[RFC][PATCH] fswatch + audit + inotify status patch

Timothy R. Chavez tinytim at us.ibm.com
Wed Jul 20 21:56:39 UTC 2005


Ahoy,

Here's an update with a quick blurb in the form of a patch to just
peruse and perhaps comment on to keep things going...

The major work being done _right now_ is two-fold.  

First fold:

Merging in the common and useful parts of Inotify and audit into a 
cohesive, generic system. You'll see this work in fs/fswatch.c and 
include/linux/fswatch.h mostly.  Currently the framework is trying to 
support both statically allocated, embedded watches and dynamically 
allocated stand-alone watches (though I've not really tested the 
dynamically allocated aspect of the system yet).  I'm thinking about 
ditching the dynamically allocated watches... but perhaps this feature 
makes the framework more robust or at the very least, appealing?  

Second fold:
The conversion of fs/auditfs.c into using this framework.  I think I am mostly 
there.  In the generic watches struct we have two callback functions, one 
that's called when a watch is accessed and its user wants to be notified of 
that action and the other is called when the watch is destroyed and needs to 
know how to clean up.  This may need to expand into an array of callbacks so 
that each fswatch hook can call a custom-taylored callback function instead 
of one.

The major pieces I'm working on this week (and will hopefully finish):

* Finish up (debugging) watch insertions.
* To test and debug watch removals.
* To test and debug the watch destroy callback.

The major pieces I will be working on this weekend and next week:
* Modifying the list traversal code such that it works with the new
(and improved?) auditfs way of doing things.
* Finish the "movement" and "change" actions.

Future work:

Then there's the necessity to change dnotify and inotify to use this new 
framework which I would be interested in recruiting help for.  I anticipate 
some work around making it so this system interfaces nicely with Inotify 
without Inotify having to make any drastic (or more drastic) changes then
auditfs has made.


Once I've completed the initial fswatch framework and adapted auditfs to 
use it, I'll provide a more detailed description of the fswatch framework and 
CC those people (external) to this list that might be interested in this 
milestone.

Warning: This might not be the cleanest patch (and is a work-in-progress)

-tim

fswatch work

---
commit 812607888b862038eb4df745ea9fe0706df92252
tree 16ba8dde73a7bf4dd73b0b3093ca2d220bd7cd48
parent 39299d9d15c41cbdd7c7009967cd35afaf34d8fa
author root <root at liltux.austin.ibm.com> Wed, 20 Jul 2011 03:10:24 -0500
committer root <root at liltux.austin.ibm.com> Wed, 20 Jul 2011 03:10:24 -0500

 fs/Kconfig                  |    6 
 fs/Makefile                 |    1 
 fs/attr.c                   |    4 
 fs/compat.c                 |    6 
 fs/dcache.c                 |    8 +
 fs/file_table.c             |    4 
 fs/fswatch.c                |  672 +++++++++++++++++++++++++++++++++++++++++++
 fs/inode.c                  |    9 -
 fs/namei.c                  |   23 +
 fs/nfsd/vfs.c               |    8 -
 fs/open.c                   |    6 
 fs/read_write.c             |   10 -
 fs/sysfs/file.c             |    5 
 fs/xattr.c                  |    4 
 include/linux/audit.h       |   55 ++++
 include/linux/dnotify.h     |  101 ++++++
 include/linux/fs.h          |    2 
 include/linux/fswatch.h     |  118 ++++++++
 init/Kconfig                |   10 +
 kernel/Makefile             |    1 
 kernel/audit.c              |   23 +
 kernel/auditfs.c            |  329 +++++++++++++++++++++
 kernel/auditsc.c            |  117 +++++++
 security/selinux/nlmsgtab.c |    3 
 24 files changed, 1481 insertions(+), 44 deletions(-)

diff --git a/fs/Kconfig b/fs/Kconfig
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -408,6 +408,12 @@ config QUOTACTL
 	depends on XFS_QUOTA || QUOTA
 	default y
 
+config FSWATCH
+        bool "Generic file watches"
+        help
+          This is a generic framework for monitoring file system
+          activity.
+
 config DNOTIFY
 	bool "Dnotify support" if EMBEDDED
 	default y
diff --git a/fs/Makefile b/fs/Makefile
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_QFMT_V1)		+= quota_v1.o
 obj-$(CONFIG_QFMT_V2)		+= quota_v2.o
 obj-$(CONFIG_QUOTACTL)		+= quota.o
 
+obj-$(CONFIG_FSWATCH)		+= fswatch.o
 obj-$(CONFIG_DNOTIFY)		+= dnotify.o
 
 obj-$(CONFIG_PROC_FS)		+= proc/
diff --git a/fs/attr.c b/fs/attr.c
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -10,7 +10,7 @@
 #include <linux/mm.h>
 #include <linux/string.h>
 #include <linux/smp_lock.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <linux/fcntl.h>
 #include <linux/quotaops.h>
 #include <linux/security.h>
@@ -175,7 +175,7 @@ int notify_change(struct dentry * dentry
 		up_write(&dentry->d_inode->i_alloc_sem);
 
 	if (!error)
-		fsnotify_change(dentry, ia_valid);
+		fswatch_notify_change(dentry, ia_valid);
 
 	return error;
 }
diff --git a/fs/compat.c b/fs/compat.c
--- a/fs/compat.c
+++ b/fs/compat.c
@@ -37,7 +37,7 @@
 #include <linux/ctype.h>
 #include <linux/module.h>
 #include <linux/dirent.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <linux/highuid.h>
 #include <linux/sunrpc/svc.h>
 #include <linux/nfsd/nfsd.h>
@@ -1310,9 +1310,9 @@ out:
 	if ((ret + (type == READ)) > 0) {
 		struct dentry *dentry = file->f_dentry;
 		if (type == READ)
-			fsnotify_access(dentry);
+			fswatch_notify_access(dentry);
 		else
-			fsnotify_modify(dentry);
+			fswatch_notify_modify(dentry);
 	}
 	return ret;
 }
diff --git a/fs/dcache.c b/fs/dcache.c
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -32,6 +32,7 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/fswatch.h>
 
 /* #define DCACHE_DEBUG 1 */
 
@@ -97,6 +98,7 @@ static inline void dentry_iput(struct de
 {
 	struct inode *inode = dentry->d_inode;
 	if (inode) {
+		fswatch_update_watch(dentry, 1);
 		dentry->d_inode = NULL;
 		list_del_init(&dentry->d_alias);
 		spin_unlock(&dentry->d_lock);
@@ -802,6 +804,7 @@ void d_instantiate(struct dentry *entry,
 	if (inode)
 		list_add(&entry->d_alias, &inode->i_dentry);
 	entry->d_inode = inode;
+	fswatch_update_watch(entry, 0);
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
 }
@@ -987,6 +990,7 @@ struct dentry *d_splice_alias(struct ino
 			/* d_instantiate takes dcache_lock, so we do it by hand */
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
+			fswatch_update_watch(dentry, 0);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
@@ -1090,6 +1094,7 @@ struct dentry * __d_lookup(struct dentry
 		if (!d_unhashed(dentry)) {
 			atomic_inc(&dentry->d_count);
 			found = dentry;
+			fswatch_update_watch(dentry, 0);
 		}
 		spin_unlock(&dentry->d_lock);
 		break;
@@ -1299,6 +1304,8 @@ void d_move(struct dentry * dentry, stru
 		spin_lock(&target->d_lock);
 	}
 
+	fswatch_update_watch(dentry, 1);
+
 	/* Move the dentry to the target hash queue, if on different bucket */
 	if (dentry->d_flags & DCACHE_UNHASHED)
 		goto already_unhashed;
@@ -1332,6 +1339,7 @@ already_unhashed:
 		list_add(&target->d_child, &target->d_parent->d_subdirs);
 	}
 
+	fswatch_update_watch(dentry, 0);
 	list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
 	spin_unlock(&target->d_lock);
 	spin_unlock(&dentry->d_lock);
diff --git a/fs/file_table.c b/fs/file_table.c
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -16,7 +16,7 @@
 #include <linux/eventpoll.h>
 #include <linux/mount.h>
 #include <linux/cdev.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 
 /* sysctl tunables... */
 struct files_stat_struct files_stat = {
@@ -128,7 +128,7 @@ void fastcall __fput(struct file *file)
 
 	might_sleep();
 
-	fsnotify_close(file);
+	fswatch_notify_close(file);
 	/*
 	 * The function eventpoll_release() should be the first called
 	 * in the file cleanup chain.
diff --git a/fs/fswatch.c b/fs/fswatch.c
new file mode 100644
--- /dev/null
+++ b/fs/fswatch.c
@@ -0,0 +1,672 @@
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list.h>
+#include <linux/hash.h>
+#include <linux/slab.h>
+#include <linux/fswatch.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+
+/* Locks */
+static spinlock_t fswatch_lock = SPIN_LOCK_UNLOCKED;
+static spinlock_t watch_hash_lock = SPIN_LOCK_UNLOCKED;
+extern spinlock_t inode_lock;
+
+/* Slab */
+static kmem_cache_t *watch_cache;
+
+/* Hash table */
+static int fswatch_nr_watches;
+static int fswatch_pool_size;
+static struct watch_inode_data *fswatch_data_pool;
+static struct watch_inode_data **fswatch_hash_table;
+static int fswatch_hash_bits;
+static int fswatch_cache_buckets = 16384;
+module_param(fswatch_cache_buckets, int, 0);
+MODULE_PARM_DESC(fswatch_cache_buckets,
+		 "Number of watch cache entries to alloc (default 16384)\n");
+
+static void fswatch_put_data(struct watch_inode_data *data);
+
+static int fswatch_data_pool_grow(void)
+{
+	struct watch_inode_data *new;
+
+	new = kmalloc(sizeof(*new), GFP_KERNEL);
+	if (!new)
+		return -ENOMEM;
+	new->next_hash = kmalloc(sizeof(*new), GFP_KERNEL);
+	if (!new->next_hash) {
+		kfree(new);
+		return -ENOMEM;
+	}
+
+	spin_lock(&watch_hash_lock);
+	new->next_hash->next_hash = fswatch_data_pool;
+	fswatch_data_pool = new;
+	fswatch_nr_watches++;
+	fswatch_pool_size += 2;
+	spin_unlock(&watch_hash_lock);
+	return 0;
+}
+
+static void fswatch_data_pool_shrink(void)
+{
+	spin_lock(&watch_hash_lock);
+	fswatch_nr_watches--;
+
+	while (fswatch_pool_size > fswatch_nr_watches + 1) {
+		struct watch_inode_data *old = fswatch_data_pool;
+		fswatch_data_pool = old->next_hash;
+		fswatch_pool_size--;
+		kfree(old);
+	}
+	spin_unlock(&watch_hash_lock);
+}
+
+static struct watch_inode_data *fswatch_get_data(struct inode *inode, int allocate)
+{
+	struct watch_inode_data **list;
+	struct watch_inode_data *ret = NULL;
+	int h;
+
+	spin_lock(&watch_hash_lock);
+
+	/* I_WATCH bit can only be changed under watch_hash_lock, so no need
+	   to lock inode_lock (on all known hardware) */
+	if (!allocate && !(inode->i_state & I_WATCH))
+		goto out;
+
+	h = hash_ptr(inode, fswatch_hash_bits);
+	list = &fswatch_hash_table[h];
+
+	while (*list && (unsigned long)((*list)->inode) < (unsigned long)inode)
+		list = &(*list)->next_hash;
+	if (*list && (*list)->inode == inode)
+		ret = *list;
+
+	if (ret) {
+		ret->count++;
+	} else if (allocate) {
+		ret = fswatch_data_pool;
+		fswatch_data_pool = ret->next_hash;
+		fswatch_pool_size--;
+
+		INIT_HLIST_HEAD(&ret->watchlist);
+		INIT_HLIST_HEAD(&ret->watches);
+		ret->inode = inode;
+		ret->next_hash = *list;
+		ret->count = 2;
+		*list = ret;
+
+		spin_lock(&inode_lock);
+		inode->i_state |= I_WATCH;
+		spin_unlock(&inode_lock);
+	}
+ out:
+	spin_unlock(&watch_hash_lock);
+
+	return ret;
+}
+
+static inline struct watch *fswatch_fetch_watch(const char *name,
+						struct watch_inode_data *data,
+						uint user)
+{
+	struct watch *watch, *ret = NULL;
+	struct hlist_node *pos;
+
+	hlist_for_each_entry(watch, pos, &data->watchlist, w_node)
+		if (!strcmp(watch->w_name, name)) {
+			ret = fswatch_get_watch(watch);
+			break;
+		}
+
+	return ret;
+}
+
+static inline struct watch *fswatch_fetch_watch_lock(const char *name,
+						     struct watch_inode_data *data,
+						     uint user)
+{
+	struct watch *ret = NULL;
+
+	if (name && data) {
+		spin_lock(&fswatch_lock);
+		ret = fswatch_fetch_watch(name, data, user);
+		spin_unlock(&fswatch_lock);
+	}
+
+	return ret;
+}
+
+struct watch *fswatch_create_watch(char *name, uint user, uint mask,
+				   void (*notify)(struct watch *, struct inode *, uint),
+				   void (*destroy)(struct watch *))
+{
+	struct watch *watch;
+
+	watch = kmem_cache_alloc(watch_cache, GFP_KERNEL);
+	if (watch) {
+		memset(watch, 0, sizeof(*watch));
+		/* We take the first reference when this watch is inserted
+		 * into a list */
+		atomic_set(&watch->w_count, 0);
+	}
+
+	return watch;
+}
+
+void fswatch_free_watch(struct watch *watch)
+{
+	BUG_ON(!watch);
+	BUG_ON(!hlist_unhashed(&watch->w_node));
+	BUG_ON(!hlist_unhashed(&watch->w_watched));
+
+	kfree(watch->w_name);
+	if (watch->w_destroy)
+		watch->w_destroy(watch);
+	else
+		kmem_cache_free(watch_cache, watch);
+}
+
+static inline void fswatch_destroy_watch(struct watch *watch)
+{
+	BUG_ON(!watch);
+
+	if (!hlist_unhashed(&watch->w_watched)) {
+		hlist_del_init(&watch->w_watched);
+		fswatch_put_watch(watch);
+	}
+
+	if (!hlist_unhashed(&watch->w_node)) {
+		hlist_del_init(&watch->w_node);
+		fswatch_put_watch(watch);
+	}
+}
+
+static inline void fswatch_drain_watchlist(struct watch_inode_data *data)
+{
+	struct watch *watch;
+	struct hlist_node *pos, *tmp;
+
+	spin_lock(&fswatch_lock);
+	hlist_for_each_entry_safe(watch, pos, tmp, &data->watchlist, w_node) {
+		fswatch_destroy_watch(watch);
+		fswatch_data_pool_shrink();
+		/* Add notification that fswatch was removed implicitly */
+	}
+	spin_unlock(&fswatch_lock);
+}
+
+static void fswatch_data_unhash(struct watch_inode_data *data)
+{
+	int h = hash_ptr(data->inode, fswatch_hash_bits);
+	struct watch_inode_data **list = &fswatch_hash_table[h];
+
+	while (*list && (unsigned long)((*list)->inode) < (unsigned long)data->inode)
+		list = &(*list)->next_hash;
+
+	BUG_ON(*list != data);
+	*list = data->next_hash;
+
+	spin_lock(&inode_lock);
+	data->inode->i_state &= ~I_WATCH;
+	spin_unlock(&inode_lock);
+	data->inode = NULL;
+}
+
+static void fswatch_put_data(struct watch_inode_data *data)
+{
+	if (!data)
+		return;
+
+	spin_lock(&watch_hash_lock);
+	data->count--;
+
+	if (data->count == 1 && data->inode &&
+	    hlist_empty(&data->watches) && hlist_empty(&data->watchlist)) {
+		data->count--;
+	}
+
+	if (!data->count) {
+		/* We are last user. Remove it from the hash table to
+		   disassociate it from its inode */
+		if (data->inode)
+			fswatch_data_unhash(data);
+		spin_unlock(&watch_hash_lock);
+
+		fswatch_drain_watchlist(data);
+
+		spin_lock(&watch_hash_lock);
+		/* Check whether to free it or return it to the pool */
+		if (fswatch_nr_watches > fswatch_pool_size) {
+			data->next_hash = fswatch_data_pool;
+			fswatch_data_pool = data;
+			fswatch_pool_size++;
+		} else {
+			kfree(data);
+		}
+	}
+	spin_unlock(&watch_hash_lock);
+}
+
+/*
+ * The update hook is responsible for watching and unwatching d_inodes during
+ * their lifetimes in dcache.  Each d_inode being watched is pinned in memory.
+ * As soon as a d_inode becomes unwatched (ie: dentry is destroyed, watch is
+ * unhashed / removed from watchlist, dentry is moved out of watch path).
+ *
+ * Hook appears in fs/dcache.c:
+ *	d_move(),
+ * 	dentry_iput(),
+ *	d_instantiate(),
+ *	d_splice_alias()
+ *	__d_lookup()
+ */
+void fswatch_update_watch(struct dentry *dentry, int remove)
+{
+	struct watch *this;
+	struct watch *watch;
+	struct watch_inode_data *data = NULL;
+	struct watch_inode_data *parent = NULL;
+	struct hlist_node *wpos;
+	struct hlist_node *tpos;
+	struct hlist_node *tmp;
+
+	if (!dentry || !dentry->d_inode)
+		return;
+
+	if (!dentry->d_parent || !dentry->d_parent->d_inode)
+		return;
+
+	/* If there's no watch data on the parent inode, then there can
+	   be no watches to add or remove */
+	parent = fswatch_get_data(dentry->d_parent->d_inode, 0);
+	if (!parent)
+		return;
+
+	spin_lock(&fswatch_lock);
+	/* Must update every watch here */
+	hlist_for_each_entry(watch, wpos, &parent->watchlist, w_node) {
+		watch = fswatch_get_watch(watch);
+		spin_unlock(&fswatch_lock);
+		if (strcmp(watch->w_name, dentry->d_name.name)) {
+			fswatch_put_watch(watch);
+			spin_lock(&fswatch_lock);
+			continue;
+		}
+		/* Fetch watch data, using the preallocated one from the watch
+		   if there is actually a relevant watch and the inode didn't
+		   already have any fswatch data */
+		data = fswatch_get_data(dentry->d_inode, !!watch);
+
+		/* If there's no data, then there wasn't a watch either.
+	           Nothing to see here; move along */
+		if (!data) {
+			fswatch_put_watch(watch);
+			goto put_fswatch;
+		}
+
+		spin_lock(&fswatch_lock);
+		if (remove) {
+			if (watch && !hlist_unhashed(&watch->w_watched)) {
+				hlist_del_init(&watch->w_watched);
+				fswatch_put_watch(watch);
+			}
+		} else {
+			hlist_for_each_entry_safe(this, tpos, tmp, &data->watches, w_watched) {
+				if (hlist_unhashed(&this->w_node)) {
+					hlist_del(&this->w_watched);
+					fswatch_put_watch(this);
+				}
+			}
+			if (watch && hlist_unhashed(&watch->w_watched)) {
+				hlist_add_head(&watch->w_watched, &data->watches);
+				fswatch_get_watch(watch);
+			}
+		}
+		fswatch_put_watch(watch);
+		fswatch_put_data(data);
+
+	}
+	spin_unlock(&fswatch_lock);
+
+put_fswatch:
+	fswatch_put_data(parent);
+}
+
+int fswatch_add_watch(char *path, struct watch *watch)
+{
+	int ret;
+	struct nameidata nd;
+	struct watch_inode_data *pdata;
+	struct watch *lookup;
+
+	/* Grow the pool by two -- one for the fswatch itself, and
+	   one for the parent directory */
+	if (fswatch_data_pool_grow())
+		return -ENOMEM;
+
+	ret = path_lookup(path, LOOKUP_PARENT, &nd);
+	if (ret < 0)
+		goto out;
+
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM || !nd.last.name)
+		goto release;
+
+	ret = -ENOMEM;
+	watch->w_name = kmalloc(strlen(nd.last.name)+1, GFP_KERNEL);
+	if (!watch->w_name)
+		goto release;
+
+	strcpy(watch->w_name, nd.last.name);
+	watch->w_dev = nd.dentry->d_inode->i_sb->s_dev;
+
+	pdata = fswatch_get_data(nd.dentry->d_inode, 1);
+	if (!pdata)
+		goto put_pdata;
+
+	ret = -EEXIST;
+	lookup = fswatch_fetch_watch_lock(nd.last.name, pdata, watch->w_user);
+	if (lookup) {
+		fswatch_put_watch(lookup);
+		goto put_pdata;
+	}
+
+	ret = 0;
+	spin_lock(&fswatch_lock);
+	fswatch_get_watch(watch);
+	hlist_add_head(&watch->w_node, &pdata->watchlist);
+	spin_unlock(&fswatch_lock);
+
+	/* __d_lookup will attach the audit data, if nd.last exists. */
+	dput(d_lookup(nd.dentry, &nd.last));
+
+put_pdata:
+	fswatch_put_data(pdata);
+release:
+	path_release(&nd);
+out:
+	if (ret)
+		fswatch_data_pool_shrink();
+	return ret;
+}
+
+int fswatch_rem_watch(char *path, uint user)
+{
+	int ret = 0;
+	struct nameidata nd;
+	struct watch_inode_data *pdata = NULL;
+	struct watch *watch;
+
+	ret = path_lookup(path, LOOKUP_PARENT, &nd);
+	if (ret < 0)
+		goto out;
+
+	ret = -ENOENT;
+	if (nd.last_type != LAST_NORM || !nd.last.name)
+		goto release;
+
+	pdata = fswatch_get_data(nd.dentry->d_inode, 0);
+	if (!pdata)
+		goto release;
+
+	spin_lock(&fswatch_lock);
+	watch = fswatch_fetch_watch(nd.last.name, pdata, user);
+	if (!watch) {
+		spin_unlock(&fswatch_lock);
+		goto release;
+	}
+
+	ret = 0;
+	fswatch_destroy_watch(watch);
+	fswatch_put_watch(watch);
+	spin_unlock(&fswatch_lock);
+
+release:
+	path_release(&nd);
+out:
+	fswatch_put_data(pdata);
+	if (!ret)
+		fswatch_data_pool_shrink();
+	return ret;
+}
+
+struct watch *fswatch_get_watch(struct watch *watch)
+{
+        int new;
+
+	BUG_ON(!watch);
+
+	new = atomic_inc_return(&watch->w_count);
+
+	return watch;
+}
+
+void fswatch_put_watch(struct watch *watch)
+{
+	int new;
+
+	BUG_ON(!watch);
+
+	new = atomic_dec_return(&watch->w_count);
+	if (!new)
+		fswatch_free_watch(watch);
+}
+
+void fswatch_inode_free(struct inode *inode)
+{
+	struct watch *watch;
+	struct hlist_node *pos, *tmp;
+	struct watch_inode_data *data = fswatch_get_data(inode, 0);
+
+	if (data) {
+		spin_lock(&watch_hash_lock);
+		fswatch_data_unhash(data);
+		spin_unlock(&watch_hash_lock);
+
+		fswatch_drain_watchlist(data);
+		/* Release all our references to any watches we may have on us */
+		spin_lock(&fswatch_lock);
+		hlist_for_each_entry_safe(watch, pos, tmp, &data->watches, w_watched) {
+			hlist_del(&watch->w_watched);
+                	fswatch_put_watch(watch);
+        	}
+		spin_unlock(&fswatch_lock);
+		fswatch_put_data(data);
+	}
+}
+
+int fswatch_init(void)
+{
+
+	watch_cache = kmem_cache_create("watch_cache", sizeof(struct watch),
+					0, 0, NULL, NULL);
+	if (!watch_cache)
+		goto fswatch_init_fail;
+
+	/* Set up hash table for inode objects */
+	fswatch_hash_bits = long_log2(fswatch_cache_buckets);
+	if (fswatch_cache_buckets != (1 << fswatch_hash_bits)) {
+		fswatch_hash_bits++;
+		fswatch_cache_buckets = 1 << fswatch_hash_bits;
+		printk(KERN_NOTICE
+		       "%s: fswatch_cache_buckets set to %d (bits %d)\n",
+		       __FUNCTION__, fswatch_cache_buckets, fswatch_hash_bits);
+	}
+
+	fswatch_hash_table = kmalloc(fswatch_cache_buckets * sizeof(void *), GFP_KERNEL);
+
+	if (!fswatch_hash_table) {
+		printk(KERN_NOTICE "No memory to initialize fswatch cache.\n");
+		goto fswatch_init_fail;
+	}
+
+	memset(fswatch_hash_table, 0, fswatch_cache_buckets * sizeof(void *));
+
+	return 0;
+
+fswatch_init_fail:
+	kmem_cache_destroy(watch_cache);
+	return -ENOMEM;
+}
+
+
+/*
+ * Notify all users with watches on this inode, interested in this action
+ */
+void fswatch_notify_users(struct inode *inode, const void *aux, uint action)
+{
+	struct watch *watch;
+	struct watch_inode_data *data = NULL;
+	struct hlist_node *pos;
+
+	data = fswatch_get_data(inode, 0);
+        if (!data)
+		return;
+
+	spin_lock(&fswatch_lock);
+	hlist_for_each_entry(watch, pos, &data->watches, w_watched) {
+		fswatch_get_watch(watch);
+		spin_unlock(&fswatch_lock);
+		if (!watch->w_mask || watch->w_mask&action)
+			watch->w_notify(watch, aux, inode, action);
+		fswatch_put_watch(watch);
+		spin_lock(&fswatch_lock);
+	}
+	spin_unlock(&fswatch_lock);
+}
+
+/*
+ * fswatch_notify_move - file old_name at old_dir was moved to new_name at new_dir
+ */
+
+/*
+ * fswatch_notify_unlink - file was unlinked
+ */
+void fswatch_notify_unlink(struct dentry *dentry, struct inode *dir)
+{
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+
+	fswatch_notify_users(inode, name, FSW_DELETE_SELF);
+	fswatch_notify_users(dir, name, FSW_DELETE);
+}
+
+/*
+ * fswatch_notify_rmdir - directory was removed
+ */
+void fswatch_notify_rmdir(struct dentry *dentry, struct inode *inode,
+			  struct inode *dir)
+{
+	const char *name = dentry->d_name.name;
+
+	fswatch_notify_users(inode, name, FSW_DELETE_SELF);
+	fswatch_notify_users(dir, name, FSW_DELETE);
+	return;
+}
+
+/*
+ * fswatch_notify_create - 'name' was linked in
+ */
+void fswatch_notify_create(struct inode *inode, const char *name)
+{
+	fswatch_notify_users(inode, name, FSW_CREATE);
+	return;
+}
+
+/*
+ * fswatch_notify_mkdir - directory 'name' was created
+ */
+void fswatch_notify_mkdir(struct inode *inode, const char *name)
+{
+	fswatch_notify_users(inode, name, FSW_CREATE);
+	return;
+}
+
+/*
+ * fswatch_notify_access - file was read
+ */
+void fswatch_notify_access(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+
+	fswatch_notify_users(inode, name, FSW_ACCESS);
+	return;
+}
+EXPORT_SYMBOL_GPL(fswatch_notify_access);
+
+/*
+ * fswatch_notify_modify - file was modified
+ */
+void fswatch_notify_modify(struct dentry *dentry)
+{
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+
+	fswatch_notify_users(inode, name, FSW_MODIFY);
+	return;
+}
+EXPORT_SYMBOL_GPL(fswatch_notify_modify);
+
+/*
+ * fswatch_notify_open - file was opened
+ */
+void fswatch_notify_open(struct dentry *dentry, int mask)
+{
+	struct inode *inode = dentry->d_inode;
+	const char *name = dentry->d_name.name;
+
+	fswatch_notify_users(inode, name, FSW_OPEN);
+	return;
+}
+
+/*
+ * fswatch_notify_close - file was closed
+ */
+void fswatch_notify_close(struct file *file)
+{
+	struct dentry *dentry = file->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	struct inode *parent = dentry->d_parent->d_inode;
+	const char *name = dentry->d_name.name;
+
+	if (file->f_mode & FMODE_WRITE) {
+		fswatch_notify_users(inode, name, FSW_CLOSE_WRITE);
+		fswatch_notify_users(parent, name, FSW_CLOSE_WRITE);
+	} else {
+		fswatch_notify_users(inode, name, FSW_CLOSE_NOWRITE);
+		fswatch_notify_users(parent, name, FSW_CLOSE_NOWRITE);
+	}
+	return;
+}
+
+/*
+ * fswatch_notify_xattr - extended attributes were changed
+ */
+void fswatch_notify_xattr(struct dentry *dentry)
+{
+        struct inode *inode = dentry->d_inode;
+	struct inode *parent = dentry->d_parent->d_inode;
+        const char *name = dentry->d_name.name;
+
+	fswatch_notify_users(inode, name, FSW_ATTRIB);
+	fswatch_notify_users(parent, name, FSW_ATTRIB);
+	return;
+}
+
+/*
+ * fswatch_notify_change - notify_change event.  file was modified and/or metadata
+ * was changed.
+ */
+void fswatch_notify_change(struct dentry *dentry, unsigned int ia_valid)
+{
+	return;
+}
diff --git a/fs/inode.c b/fs/inode.c
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -22,6 +22,8 @@
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
 #include <linux/inotify.h>
+#include <linux/fswatch.h>
+#include <linux/audit.h>
 
 /*
  * This is needed for the following functions:
@@ -173,6 +175,7 @@ void destroy_inode(struct inode *inode) 
 {
 	if (inode_has_buffers(inode))
 		BUG();
+	fswatch_inode_free(inode);
 	security_inode_free(inode);
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
@@ -262,7 +265,7 @@ void clear_inode(struct inode *inode)
 		bd_forget(inode);
 	if (inode->i_cdev)
 		cd_forget(inode);
-	inode->i_state = I_CLEAR;
+	inode->i_state = I_CLEAR | (inode->i_state & I_WATCH);
 }
 
 EXPORT_SYMBOL(clear_inode);
@@ -1051,7 +1054,7 @@ void generic_delete_inode(struct inode *
 	hlist_del_init(&inode->i_hash);
 	spin_unlock(&inode_lock);
 	wake_up_inode(inode);
-	if (inode->i_state != I_CLEAR)
+	if ((inode->i_state & ~I_WATCH) != I_CLEAR)
 		BUG();
 	destroy_inode(inode);
 }
@@ -1363,6 +1366,8 @@ void __init inode_init(unsigned long mem
 	inode_cachep = kmem_cache_create("inode_cache", sizeof(struct inode),
 				0, SLAB_RECLAIM_ACCOUNT|SLAB_PANIC, init_once, NULL);
 	set_shrinker(DEFAULT_SEEKS, shrink_icache_memory);
+	fswatch_init();
+	audit_filesystem_init();
 
 	/* Hash may have been set up in inode_init_early */
 	if (!hashdist)
diff --git a/fs/namei.c b/fs/namei.c
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -21,7 +21,7 @@
 #include <linux/namei.h>
 #include <linux/quotaops.h>
 #include <linux/pagemap.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
 #include <linux/security.h>
@@ -1312,7 +1312,7 @@ int vfs_create(struct inode *dir, struct
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
 	if (!error) {
-		fsnotify_create(dir, dentry->d_name.name);
+		fswatch_notify_create(dir, dentry->d_name.name);
 		security_inode_post_create(dir, dentry, mode);
 	}
 	return error;
@@ -1637,7 +1637,7 @@ int vfs_mknod(struct inode *dir, struct 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
 	if (!error) {
-		fsnotify_create(dir, dentry->d_name.name);
+		fswatch_notify_create(dir, dentry->d_name.name);
 		security_inode_post_mknod(dir, dentry, mode, dev);
 	}
 	return error;
@@ -1710,7 +1710,7 @@ int vfs_mkdir(struct inode *dir, struct 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mkdir(dir, dentry, mode);
 	if (!error) {
-		fsnotify_mkdir(dir, dentry->d_name.name);
+		fswatch_notify_mkdir(dir, dentry->d_name.name);
 		security_inode_post_mkdir(dir,dentry, mode);
 	}
 	return error;
@@ -1801,7 +1801,7 @@ int vfs_rmdir(struct inode *dir, struct 
 	}
 	up(&dentry->d_inode->i_sem);
 	if (!error) {
-		fsnotify_rmdir(dentry, dentry->d_inode, dir);
+		fswatch_notify_rmdir(dentry, dentry->d_inode, dir);
 		d_delete(dentry);
 	}
 	dput(dentry);
@@ -1874,7 +1874,7 @@ int vfs_unlink(struct inode *dir, struct
 
 	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
 	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
-		fsnotify_unlink(dentry, dir);
+		fswatch_notify_unlink(dentry, dir);
 		d_delete(dentry);
 	}
 
@@ -1951,7 +1951,7 @@ int vfs_symlink(struct inode *dir, struc
 	DQUOT_INIT(dir);
 	error = dir->i_op->symlink(dir, dentry, oldname);
 	if (!error) {
-		fsnotify_create(dir, dentry->d_name.name);
+		fswatch_notify_create(dir, dentry->d_name.name);
 		security_inode_post_symlink(dir, dentry, oldname);
 	}
 	return error;
@@ -2024,7 +2024,7 @@ int vfs_link(struct dentry *old_dentry, 
 	error = dir->i_op->link(old_dentry, dir, new_dentry);
 	up(&old_dentry->d_inode->i_sem);
 	if (!error) {
-		fsnotify_create(dir, new_dentry->d_name.name);
+		fswatch_notify_create(dir, new_dentry->d_name.name);
 		security_inode_post_link(old_dentry, dir, new_dentry);
 	}
 	return error;
@@ -2188,7 +2188,6 @@ int vfs_rename(struct inode *old_dir, st
 {
 	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;
@@ -2210,17 +2209,13 @@ int vfs_rename(struct inode *old_dir, st
 	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) {
-		const char *new_name = old_dentry->d_name.name;
-		fsnotify_move(old_dir, new_dir, old_name, new_name, is_dir);
+		//fswatch_notify_move(old_dir, new_dir, old_name, new_name, is_dir);
 	}
-	fsnotify_oldname_free(old_name);
 
 	return error;
 }
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -45,7 +45,7 @@
 #endif /* CONFIG_NFSD_V3 */
 #include <linux/nfsd/nfsfh.h>
 #include <linux/quotaops.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
 #ifdef CONFIG_NFSD_V4
@@ -860,8 +860,8 @@ nfsd_vfs_read(struct svc_rqst *rqstp, st
 		nfsdstats.io_read += err;
 		*count = err;
 		err = 0;
-		fsnotify_access(file->f_dentry);
-	} else 
+		fswatch_notify_access(file->f_dentry);
+	} else
 		err = nfserrno(err);
 out:
 	return err;
@@ -916,7 +916,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
 	set_fs(oldfs);
 	if (err >= 0) {
 		nfsdstats.io_write += cnt;
-		fsnotify_modify(file->f_dentry);
+		fswatch_notify_modify(file->f_dentry);
 	}
 
 	/* clear setuid/setgid flag after write */
diff --git a/fs/open.c b/fs/open.c
--- a/fs/open.c
+++ b/fs/open.c
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/smp_lock.h>
 #include <linux/quotaops.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
@@ -951,7 +951,7 @@ asmlinkage long sys_open(const char __us
 				put_unused_fd(fd);
 				fd = PTR_ERR(f);
 			} else {
-				fsnotify_open(f->f_dentry);
+				fswatch_notify_open(f->f_dentry, mode);
 				fd_install(fd, f);
 			}
 		}
@@ -990,7 +990,7 @@ int filp_close(struct file *filp, fl_own
 	if (filp->f_op && filp->f_op->flush)
 		retval = filp->f_op->flush(filp);
 
-	dnotify_flush(filp, id);
+	//dnotify_flush(filp, id);
 	locks_remove_posix(filp, id);
 	fput(filp);
 	return retval;
diff --git a/fs/read_write.c b/fs/read_write.c
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -10,7 +10,7 @@
 #include <linux/file.h>
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <linux/security.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
@@ -252,7 +252,7 @@ ssize_t vfs_read(struct file *file, char
 			else
 				ret = do_sync_read(file, buf, count, pos);
 			if (ret > 0) {
-				fsnotify_access(file->f_dentry);
+				fswatch_notify_access(file->f_dentry);
 				current->rchar += ret;
 			}
 			current->syscr++;
@@ -303,7 +303,7 @@ ssize_t vfs_write(struct file *file, con
 			else
 				ret = do_sync_write(file, buf, count, pos);
 			if (ret > 0) {
-				fsnotify_modify(file->f_dentry);
+				fswatch_notify_modify(file->f_dentry);
 				current->wchar += ret;
 			}
 			current->syscw++;
@@ -541,9 +541,9 @@ out:
 		kfree(iov);
 	if ((ret + (type == READ)) > 0) {
 		if (type == READ)
-			fsnotify_access(file->f_dentry);
+			fswatch_notify_access(file->f_dentry);
 		else
-			fsnotify_modify(file->f_dentry);
+			fswatch_notify_modify(file->f_dentry);
 	}
 	return ret;
 Efault:
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -3,8 +3,9 @@
  */
 
 #include <linux/module.h>
-#include <linux/fsnotify.h>
 #include <linux/kobject.h>
+#include <linux/fswatch.h>
+#include <linux/dnotify.h>
 #include <linux/namei.h>
 #include <asm/uaccess.h>
 #include <asm/semaphore.h>
@@ -405,7 +406,7 @@ int sysfs_update_file(struct kobject * k
 		if (victim->d_inode && 
 		    (victim->d_parent->d_inode == dir->d_inode)) {
 			victim->d_inode->i_mtime = CURRENT_TIME;
-			fsnotify_modify(victim);
+			fswatch_notify_modify(victim);
 
 			/**
 			 * Drop reference from initial sysfs_get_dentry().
diff --git a/fs/xattr.c b/fs/xattr.c
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -16,7 +16,7 @@
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/module.h>
-#include <linux/fsnotify.h>
+#include <linux/fswatch.h>
 #include <asm/uaccess.h>
 
 /*
@@ -59,7 +59,7 @@ setxattr(struct dentry *d, char __user *
 			goto out;
 		error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
 		if (!error) {
-			fsnotify_xattr(d);
+			fswatch_notify_xattr(d);
 			security_inode_post_setxattr(d, kname, kvalue, size, flags);
 		}
 out:
diff --git a/include/linux/audit.h b/include/linux/audit.h
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -24,8 +24,15 @@
 #ifndef _LINUX_AUDIT_H_
 #define _LINUX_AUDIT_H_
 
+#ifdef __KERNEL__
 #include <linux/sched.h>
 #include <linux/elf.h>
+#include <linux/fswatch.h>
+
+struct hlist_head;
+struct hlist_node;
+struct dentry;
+#endif
 
 /* The netlink messages for the audit system is divided into blocks:
  * 1000 - 1099 are for commanding the audit system
@@ -68,6 +75,7 @@
 #define AUDIT_CONFIG_CHANGE	1305	/* Audit system configuration change */
 #define AUDIT_SOCKADDR		1306	/* sockaddr copied as syscall arg */
 #define AUDIT_CWD		1307	/* Current working directory */
+#define AUDIT_FS_INODE		1308	/* File system inode */
 
 #define AUDIT_AVC		1400	/* SE Linux avc denial or grant */
 #define AUDIT_SELINUX_ERR	1401	/* Internal SE Linux Errors */
@@ -175,6 +183,9 @@
 #define AUDIT_ARCH_V850		(EM_V850|__AUDIT_ARCH_LE)
 #define AUDIT_ARCH_X86_64	(EM_X86_64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE)
 
+/* 32 byte max key size */
+#define AUDIT_FILTERKEY_MAX   32
+
 struct audit_status {
 	__u32		mask;		/* Bit mask for valid entries */
 	__u32		enabled;	/* 1 = enabled, 0 = disabled */
@@ -195,13 +206,42 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+/* Structure to transport watch data to and from the kernel */
+
+struct watch_transport {
+	__u32 dev_major;
+	__u32 dev_minor;
+	__u32 actions;
+	__u32 valid;
+	__u32 pathlen;
+	__u32 fklen;
+	char buf[0];
+};
+
+struct watch;
+
 #ifdef __KERNEL__
+struct audit_watch {
+	char *			au_name;
+	char *			au_path;
+	char *			au_filterkey;
+	uint32_t		au_actions;
+	dev_t			au_dev;
+	struct watch 		au_watch;
+	struct hlist_node 	au_master;
+};
 
 struct audit_sig_info {
 	uid_t		uid;
 	pid_t		pid;
 };
 
+struct audit_watch_info {
+	struct hlist_node 	node;
+	struct watch 		*watch;
+	uint 			action;
+};
+
 struct audit_buffer;
 struct audit_context;
 struct inode;
@@ -248,6 +288,7 @@ extern int audit_filter_user(struct netl
 #define audit_inode(n,i,f) do { ; } while (0)
 #define audit_receive_filter(t,p,u,s,d,l) ({ -EOPNOTSUPP; })
 #define auditsc_get_stamp(c,t,s) do { BUG(); } while (0)
+#define audit_receive_filter(t,p,u,s,d,l) ({ -EOPNOTSUPP; })
 #define audit_get_loginuid(c) ({ -1; })
 #define audit_ipc_perms(q,u,g,m) ({ 0; })
 #define audit_socketcall(n,a) ({ 0; })
@@ -257,6 +298,20 @@ extern int audit_filter_user(struct netl
 #define audit_filter_user(cb,t) ({ 1; })
 #endif
 
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_filesystem_init(void);
+extern int audit_list_watches(int pid, int seq);
+extern int audit_receive_watch(int type, int pid, int uid, int seq,
+			       struct watch_transport *req, uid_t loginuid);
+extern void auditfs_attach_wdata(struct watch *, const void *, struct inode *, uint);
+#else
+#define audit_filesystem_init(t) ({ 0; })
+#define audit_list_watches(p,s) ({ -EOPNOTSUPP; })
+#define audit_receive_watch(t,p,u,s,r,l) ({ -EOPNOTSUPP; })
+#define audit_update_watch(d,r) do { ; } while (0)
+#define audit_notify_watch(i,m) do { ; } while(0)
+#endif
+
 #ifdef CONFIG_AUDIT
 /* These are defined in audit.c */
 				/* Public API */
diff --git a/include/linux/dnotify.h b/include/linux/dnotify.h
--- a/include/linux/dnotify.h
+++ b/include/linux/dnotify.h
@@ -33,8 +33,109 @@ static inline void inode_dir_notify(stru
 		__inode_dir_notify(inode, event);
 }
 
+static inline void dnotify_move(struct inode *old_dir, struct inode *new_dir,
+				const char *old_name, const char *new_name,
+				int isdir)
+{
+	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);
+	}
+}
+
+static inline void dnotify_unlink(struct dentry *dentry, struct inode *dir)
+{
+	inode_dir_notify(dir, DN_DELETE);
+}
+
+static inline void dnotify_rmdir(struct dentry *dentry, struct inode *inode,
+                                 struct inode *dir)
+{
+	inode_dir_notify(dir, DN_DELETE);
+}
+
+static inline void dnotify_create(struct inode *inode, const char *name)
+{
+	inode_dir_notify(inode, DN_CREATE);
+}
+
+static inline void dnotify_mkdir(struct inode *inode, const char *name)
+{
+	inode_dir_notify(inode, DN_CREATE);
+}
+
+static inline void dnotify_access(struct dentry *dentry)
+{
+	dnotify_parent(dentry, DN_ACCESS);
+}
+
+static inline void dnotify_modify(struct dentry *dentry)
+{
+	dnotify_parent(dentry, DN_MODIFY);
+}
+
+static inline void dnotify_change(struct dentry *dentry, unsigned int ia_valid)
+{
+	int 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;
+
+	if (dn_mask)
+		dnotify_parent(dentry, dn_mask);
+}
 #else
 
+static inline void dnotify_move(struct inode *old_dir, struct inode *new_dir,
+				const char *old_name, const char *new_name,
+				int isdir)
+{
+}
+
+static inline void dnotify_unlink(struct dentry *dentry, struct inode *dir)
+{
+}
+
+static inline void dnotify_rmdir(struct dentry *dentry, struct inode *inode,
+				 struct inode *dir)
+{
+}
+
+static inline void dnotify_create(struct inode *inode, const char *name)
+{
+}
+
+static inline void dnotify_mkdir(struct inode *inode, const char *name)
+{
+}
+
+static inline void dnotify_access(struct dentry *dentry)
+{
+}
+
+static inline void dnotify_modify(struct dentry *dentry)
+{
+}
+
+static inline void dnotify_change(struct dentry *dentry, unsigned int ia_valid)
+{
+}
+
 static inline void __inode_dir_notify(struct inode *inode, unsigned long event)
 {
 }
diff --git a/include/linux/fs.h b/include/linux/fs.h
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -227,6 +227,7 @@ struct poll_table_struct;
 struct kstatfs;
 struct vm_area_struct;
 struct vfsmount;
+struct audit_inode_data;
 
 /* Used to be a macro which just called the function, now just a function */
 extern void update_atime (struct inode *);
@@ -1053,6 +1054,7 @@ struct super_operations {
 #define I_CLEAR			32
 #define I_NEW			64
 #define I_WILL_FREE		128
+#define I_WATCH                 256
 
 #define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
 
diff --git a/include/linux/fswatch.h b/include/linux/fswatch.h
new file mode 100644
--- /dev/null
+++ b/include/linux/fswatch.h
@@ -0,0 +1,118 @@
+#ifndef _FSWATCH_H
+#define _FSWATCH_H
+
+#include <linux/list.h>
+
+#define FSW_USER_AUDIT		0x0001
+#define FSW_USER_INOTIFY	0x0002
+
+#define FSW_ACCESS		0x00000001      /* File was accessed */
+#define FSW_MODIFY		0x00000002      /* File was modified */
+#define FSW_ATTRIB		0x00000004      /* Metadata changed */
+#define FSW_CLOSE_WRITE		0x00000008      /* Writtable file was closed */
+#define FSW_CLOSE_NOWRITE	0x00000010      /* Unwrittable file closed */
+#define FSW_OPEN		0x00000020      /* File was opened */
+#define FSW_MOVED_FROM		0x00000040      /* File was moved from X */
+#define FSW_MOVED_TO		0x00000080      /* File was moved to Y */
+#define FSW_CREATE		0x00000100      /* Subfile was created */
+#define FSW_DELETE		0x00000200      /* Subfile was deleted */
+#define FSW_DELETE_SELF		0x00000400      /* Self was deleted */
+
+struct inode;
+
+struct watch {
+	atomic_t		w_count;
+	uint			w_mask;		/* Action mask 		 */
+	uint			w_user;		/* Watch user		 */
+	struct hlist_node	w_node;		/* Per-directory list	 */
+	struct hlist_node	w_watched;	/* Per-inode list	 */
+	dev_t			w_dev;		/* Watch device		 */
+	char			*w_name;	/* Watch's name	 	 */
+	void			(*w_destroy)(struct watch *);
+	void 			(*w_notify)(struct watch *, const void *, struct inode *, uint);
+};
+
+struct watch_inode_data {
+        int			count;
+        struct watch_inode_data	*next_hash;	/* Watch data hash table      */
+	struct inode		*inode;		/* Inode to which it belongs  */
+	struct hlist_head	watches;	/* Watches on us	      */
+	struct hlist_head	watchlist;	/* Watches on children	      */
+};
+
+/* User */
+int fswatch_add_watch(char *, struct watch *);
+int fswatch_rem_watch(char *, uint user);
+void fswatch_put_watch(struct watch *);
+struct watch *fswatch_get_watch(struct watch *);
+struct watch *fswatch_alloc_watch(char *, char *, uint, uint,
+				  void (*)(struct watch *),
+				  void (*)(struct watch *, const void *, struct inode *, uint));
+
+/* Internals */
+int fswatch_init(void);
+void fswatch_inode_free(struct inode *);
+void fswatch_update_watch(struct dentry *, int);
+
+/* Hooks */
+#ifdef CONFIG_FSWATCH
+/* FIXME: fswatch_notify_move */
+const char *fswatch_notify_oldname_init(const char *);
+void fswatch_notify_oldname_free(const char *);
+void fswatch_notify_unlink(struct dentry *, struct inode *);
+void fswatch_notify_rmdir(struct dentry *, struct inode *, struct inode *);
+void fswatch_notify_create(struct inode *, const char *);
+void fswatch_notify_mkdir(struct inode *, const char *);
+void fswatch_notify_access(struct dentry *dentry);
+void fswatch_notify_modify(struct dentry *dentry);
+void fswatch_notify_open(struct dentry *dentry, int mask);
+void fswatch_notify_close(struct file *file);
+void fswatch_notify_xattr(struct dentry *dentry);
+void fswatch_notify_change(struct dentry *dentry, unsigned int ia_valid);
+void fswatch_notify_perms(struct inode *, int mask);
+#else
+/* FIXME: fswatch_notify_move */
+static inline const char *fswatch_notify_oldname_init(const char *)
+{
+	return NULL;
+}
+static inline void fswatch_notify_oldname_free(const char *)
+{
+}
+static inline void fswatch_notify_unlink(struct dentry *, struct inode *)
+{
+}
+static inline void fswatch_notify_rmdir(struct dentry *, struct inode *, struct inode *)
+{
+{
+static inline void fswatch_notify_create(struct inode *, const char *)
+{
+}
+static inline void fswatch_notify_mkdir(struct inode *, const char *)
+{
+}
+static inline void fswatch_notify_access(struct dentry *dentry)
+{
+}
+static inline void fswatch_notify_modify(struct dentry *dentry)
+{
+}
+static inline void fswatch_notify_open(struct dentry *dentry, int mask)
+{
+}
+static inline void fswatch_notify_close(struct file *file)
+{
+}
+static inline void fswatch_notify_xattr(struct dentry *dentry)
+{
+}
+static inline void fswatch_notify_change(struct dentry *dentry, unsigned int ia_valid)
+{
+}
+static inline void fswatch_notify_perms(struct inode *, int mask)
+{
+}
+#endif /* CONFIG_FSWATCH */
+
+#endif /* _FSWATCH_H */
+
diff --git a/init/Kconfig b/init/Kconfig
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -181,6 +181,16 @@ config AUDITSYSCALL
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable file system auditing support"
+	depends on AUDITSYSCALL && FSWATCH
+	default n
+	help
+	  Enable file system auditing for regular files and directories.
+	  When a targeted file or directory is accessed, an audit record
+	  is generated describing the inode accessed, how it was accessed,
+	  and by whom (ie: pid and system call).
+
 config HOTPLUG
 	bool "Support for hot-pluggable devices" if !ARCH_S390
 	default ARCH_S390
diff --git a/kernel/Makefile b/kernel/Makefile
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
 obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
 obj-$(CONFIG_AUDIT) += audit.o
 obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_SYSFS) += ksysfs.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
diff --git a/kernel/audit.c b/kernel/audit.c
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -111,8 +111,13 @@ static DECLARE_WAIT_QUEUE_HEAD(kauditd_w
 static DECLARE_WAIT_QUEUE_HEAD(audit_backlog_wait);
 
 /* The netlink socket is only to be read by 1 CPU, which lets us assume
- * that list additions and deletions never happen simultaneously in
- * auditsc.c */
+ * that list additions and deletions, and watch insertions never happen
+ * simultaneiously in auditsc.c and auditfs.c respectively.
+ *
+ * Even though there can only be one watch removal via netlink at a time,
+ * there is still a chance of watch removal via a hook.  In this case, the
+ * semaphore is not enough.
+ */
 DECLARE_MUTEX(audit_netlink_sem);
 
 /* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting
@@ -348,6 +353,9 @@ static int audit_netlink_ok(kernel_cap_t
 	case AUDIT_SET:
 	case AUDIT_ADD:
 	case AUDIT_DEL:
+	case AUDIT_WATCH_LIST:
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
 	case AUDIT_SIGNAL_INFO:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
@@ -464,6 +472,17 @@ static int audit_receive_msg(struct sk_b
 		audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_SIGNAL_INFO, 
 				0, 0, &sig_data, sizeof(sig_data));
 		break;
+	case AUDIT_WATCH_LIST:
+		err = audit_list_watches(pid, seq);
+		break;
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		if (nlh->nlmsg_len < sizeof(struct watch_transport))
+			return -EINVAL;
+		err = audit_receive_watch(nlh->nlmsg_type,
+					  NETLINK_CB(skb).pid,
+					  uid, seq, data, loginuid);
+		break;
 	default:
 		err = -EINVAL;
 		break;
diff --git a/kernel/auditfs.c b/kernel/auditfs.c
new file mode 100644
--- /dev/null
+++ b/kernel/auditfs.c
@@ -0,0 +1,329 @@
+/* auditfs.c -- Filesystem auditing support
+ * Implements filesystem auditing support, depends on kernel/auditsc.c
+ *
+ * Copyright 2005 International Business Machines Corp. (IBM)
+ * Copyright 2005 Red Hat, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * 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 of the License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307  USA
+ *
+ * Written by:		Timothy R. Chavez <chavezt at us.ibm.com>
+ *			David Woodhouse <dwmw2 at infradead.org>
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list.h>
+#include <linux/hash.h>
+#include <linux/slab.h>
+#include <linux/audit.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/fswatch.h>
+#include <asm/uaccess.h>
+
+
+extern int audit_enabled;
+
+spinlock_t auditfs_lock = SPIN_LOCK_UNLOCKED;
+
+static kmem_cache_t *audit_watch_cache;
+static HLIST_HEAD(master_watchlist);
+
+struct audit_skb_list {
+	struct hlist_node list;
+	void *memblk;
+	size_t size;
+};
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+	struct audit_watch *watch;
+
+	watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+	if (watch)
+		memset(watch, 0, sizeof(*watch));
+
+	return watch;
+}
+
+static inline void audit_free_watch(struct audit_watch *w)
+{
+	BUG_ON(!w);
+	BUG_ON(!hlist_unhashed(&w->au_master));
+
+	kfree(w->au_name);
+	kfree(w->au_path);
+	kfree(w->au_filterkey);
+
+}
+
+static void audit_free_watch_fn(struct watch *w)
+{
+	struct audit_watch *watch;
+
+	BUG_ON(!w);
+
+	watch = container_of(w, struct audit_watch, au_watch);
+	if (watch) {
+		spin_lock(&auditfs_lock);
+		hlist_del_init(&watch->au_master);
+		audit_free_watch(watch);
+		spin_unlock(&auditfs_lock);
+	}
+}
+
+
+/* Convert a watch_transport structure into a kernel audit_watch structure. */
+static inline struct audit_watch *audit_to_watch(void *memblk)
+{
+	unsigned int offset;
+	struct watch_transport *t;
+	struct audit_watch *watch;
+
+	watch = audit_watch_alloc();
+	if (!watch)
+		goto audit_to_watch_fail;
+
+	t = memblk;
+
+	watch->au_actions = t->actions;
+
+	offset = sizeof(struct watch_transport);
+	watch->au_filterkey = kmalloc(t->fklen+1, GFP_KERNEL);
+	if (!watch->au_filterkey)
+		goto audit_to_watch_fail;
+	watch->au_filterkey[t->fklen] = 0;
+	memcpy(watch->au_filterkey, memblk + offset, t->fklen);
+
+	offset += t->fklen;
+	watch->au_path = kmalloc(t->pathlen+1, GFP_KERNEL);
+	if (!watch->au_path)
+		goto audit_to_watch_fail;
+	watch->au_path[t->pathlen] = 0;
+	memcpy(watch->au_path, memblk + offset, t->pathlen);
+
+	return watch;
+
+audit_to_watch_fail:
+	audit_free_watch(watch);
+	return NULL;
+}
+
+/*
+ * Convert a kernel audit_watch structure into a watch_transport structure.
+ * We do this to send watch information back to user space.
+ */
+static inline void *audit_to_transport(struct audit_watch *watch, size_t size)
+{
+	struct watch_transport *t;
+	char *p;
+
+        t = kmalloc(size, GFP_KERNEL);
+        if (!t)
+                goto audit_to_transport_exit;
+
+	memset(t, 0, sizeof(*t));
+
+	t->dev_major = MAJOR(watch->au_dev);
+	t->dev_minor = MINOR(watch->au_dev);
+	t->actions = watch->au_actions;
+	t->pathlen = strlen(watch->au_path) + 1;
+
+	p = (char *)&t[1];
+
+	if (watch->au_filterkey) {
+		t->fklen = strlen(watch->au_filterkey) + 1;
+		memcpy(p, watch->au_filterkey, t->fklen);
+		p += t->fklen;
+	}
+	memcpy(p, watch->au_path, t->pathlen);
+
+audit_to_transport_exit:
+	return t;
+}
+
+static inline int audit_insert_watch(struct audit_watch *w, uid_t loginuid)
+{
+	int ret;
+
+
+	w->au_watch.w_user = FSW_USER_AUDIT;
+	w->au_watch.w_notify = auditfs_attach_wdata;
+	w->au_watch.w_destroy = audit_free_watch_fn;
+
+	ret = fswatch_add_watch(w->au_path, &w->au_watch);
+	if (ret) {
+		spin_lock(&auditfs_lock);
+		hlist_add_head(&w->au_master, &master_watchlist);
+		spin_unlock(&auditfs_lock);
+		audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+			  "auid=%u inserted watch", loginuid);
+	}
+
+	return ret;
+}
+
+static inline int audit_remove_watch(struct audit_watch *w, uid_t loginuid)
+{
+	int ret = 0;
+	struct audit_watch *this;
+	struct hlist_node *pos, *tmp;
+
+	spin_lock(&auditfs_lock);
+	hlist_for_each_entry_safe(this, pos, tmp, &master_watchlist, au_master)
+		if (!strcmp(this->au_path, w->au_path)) {
+			hlist_del_init(&this->au_master);
+			break;
+		}
+	spin_unlock(&auditfs_lock);
+
+	ret = fswatch_rem_watch(w->au_path, FSW_USER_AUDIT);
+	if (ret)
+		audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+			  "auid=%u inserted watch", loginuid);
+	audit_free_watch(w);
+
+	return ret;
+}
+
+/* Convert a watch to a audit_skb_list */
+struct audit_skb_list *audit_to_skb(struct audit_watch *watch)
+{
+	size_t size;
+	void *memblk;
+	struct audit_skb_list *entry;
+
+	/* We must include space for both "\0" */
+	size = sizeof(struct watch_transport) + strlen(watch->au_path) +
+	       strlen(watch->au_filterkey) + 2;
+
+	entry = ERR_PTR(-ENOMEM);
+	memblk = audit_to_transport(watch, size);
+	if (!memblk)
+		goto audit_queue_watch_exit;
+
+	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry) {
+		entry = ERR_PTR(-ENOMEM);
+		goto audit_queue_watch_exit;
+	}
+
+	entry->memblk = memblk;
+	entry->size = size;
+
+audit_queue_watch_exit:
+	return entry;
+}
+
+/*
+ * Read the "master watchlist" which is a watchlist of all watches in the
+ * file system and send it to user space.  There will never be concurrent
+ * readers of this list.
+ *
+ * The reference to watch will not be put back during a read upon a
+ * watch removal, until after we're done reading.  So, the potential
+ * for the rug being pulled out from under us is NIL.
+ *
+ * This list is only a "snapshot in time".  It is not gospel.
+ */
+static int audit_list_watches_fn(void *_dest)
+{
+	return 0;
+}
+
+int audit_list_watches(int pid, int seq)
+{
+	struct task_struct *tsk;
+	int *dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
+	if (!dest)
+		return -ENOMEM;
+	dest[0] = pid;
+	dest[1] = seq;
+
+	tsk = kthread_run(audit_list_watches_fn, dest, "audit_list_watches");
+	if (IS_ERR(tsk)) {
+		kfree(dest);
+		return PTR_ERR(tsk);
+	}
+
+	return 0;
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+			struct watch_transport *req, uid_t loginuid)
+{
+	int ret = 0;
+	struct audit_watch *w = NULL;
+	char *payload = (char *)&req[1];
+
+	ret = -ENAMETOOLONG;
+	if (req->pathlen >= PATH_MAX)
+		goto audit_receive_watch_exit;
+
+	if (req->fklen >= AUDIT_FILTERKEY_MAX)
+		goto audit_receive_watch_exit;
+
+	ret = -EINVAL;
+	if (req->pathlen == 0)
+		goto audit_receive_watch_exit;
+
+	if (payload[req->fklen] != '/')
+		goto audit_receive_watch_exit;
+
+	/*FIXME: Make sure "actions" are valid */
+
+	ret = -ENOMEM;
+	w = audit_to_watch(req);
+	if (!w)
+		goto audit_receive_watch_exit;
+
+	switch (type) {
+	case AUDIT_WATCH_INS:
+		ret = audit_insert_watch(w, loginuid);
+		break;
+	case AUDIT_WATCH_REM:
+		ret = audit_remove_watch(w, loginuid);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret < 0)
+		audit_free_watch(w);
+
+audit_receive_watch_exit:
+	return ret;
+}
+
+/* Can't handle error */
+int audit_filesystem_init(void)
+{
+	audit_watch_cache =
+		kmem_cache_create("audit_watch_cache",
+				  sizeof(struct audit_watch), 0, 0, NULL, NULL);
+	if (!audit_watch_cache) {
+		kmem_cache_destroy(audit_watch_cache);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -41,6 +41,7 @@
 #include <linux/time.h>
 #include <linux/kthread.h>
 #include <linux/netlink.h>
+#include <linux/fswatch.h>
 #include <linux/compiler.h>
 #include <asm/unistd.h>
 
@@ -135,6 +136,18 @@ struct audit_aux_data_path {
 	struct vfsmount		*mnt;
 };
 
+struct audit_aux_data_watched {
+	struct audit_aux_data	link;
+	struct hlist_head	watches;
+	uint			action;
+	ulong			ino;
+	umode_t			mode;
+	uid_t			uid;
+	gid_t			gid;
+	dev_t			dev;
+	dev_t			rdev;
+};
+
 /* The per-task audit context. */
 struct audit_context {
 	int		    in_syscall;	/* 1 if task is in a syscall */
@@ -665,13 +678,26 @@ static inline void audit_free_names(stru
 static inline void audit_free_aux(struct audit_context *context)
 {
 	struct audit_aux_data *aux;
+	struct audit_watch_info *winfo;
+	struct hlist_node *pos, *tmp;
 
 	while ((aux = context->aux)) {
-		if (aux->type == AUDIT_AVC_PATH) {
+		switch(aux->type) {
+		case AUDIT_AVC_PATH: {
 			struct audit_aux_data_path *axi = (void *)aux;
 			dput(axi->dentry);
 			mntput(axi->mnt);
+			break; }
+		case AUDIT_FS_INODE: {
+			struct audit_aux_data_watched *axi = (void *)aux;
+			hlist_for_each_entry_safe(winfo, pos, tmp, &axi->watches, node) {
+				fswatch_put_watch(winfo->watch);
+				hlist_del(&winfo->node);
+				kfree(winfo);
+                        }
+			break; }
 		}
+
 		context->aux = aux->next;
 		kfree(aux);
 	}
@@ -784,6 +810,8 @@ static void audit_log_exit(struct audit_
 	int i;
 	struct audit_buffer *ab;
 	struct audit_aux_data *aux;
+	struct audit_watch_info *winfo;
+	struct hlist_node *pos;
 
 	ab = audit_log_start(context, gfp_mask, AUDIT_SYSCALL);
 	if (!ab)
@@ -849,6 +877,30 @@ static void audit_log_exit(struct audit_
 			audit_log_d_path(ab, "path=", axi->dentry, axi->mnt);
 			break; }
 
+		case AUDIT_FS_INODE: {
+			struct audit_aux_data_watched *axi = (void *)aux;
+			struct audit_watch *au_watch;
+			struct audit_buffer *sub_ab;
+			audit_log_format(ab,
+					"inode=%lu mode=%u uid=%u gid=%u "
+					"dev=%02x:%02x rdev=%02x:%02x",
+					axi->ino, axi->mode, axi->uid, axi->gid,
+					MAJOR(axi->dev), MINOR(axi->dev),
+					MAJOR(axi->rdev), MINOR(axi->rdev));
+			hlist_for_each_entry(winfo, pos, &axi->watches, node) {
+				au_watch = container_of(winfo->watch, struct audit_watch, au_watch);
+				sub_ab = audit_log_start(context, GFP_KERNEL, AUDIT_FS_WATCH);
+				if (!sub_ab)
+					return;         /* audit_panic has been called */
+				audit_log_format(sub_ab, "watch=%lu", axi->ino);
+				audit_log_format(sub_ab, " name=");
+				audit_log_untrustedstring(sub_ab, winfo->watch->w_name);
+				audit_log_format(sub_ab,
+						 " filterkey=%s action=%d",
+						 au_watch->au_filterkey, winfo->action);
+				audit_log_end(sub_ab);
+			}
+			break; }
 		}
 		audit_log_end(ab);
 	}
@@ -865,9 +917,8 @@ static void audit_log_exit(struct audit_
 		if (!ab)
 			continue; /* audit_panic has been called */
 
-		audit_log_format(ab, "item=%d", i);
 		if (context->names[i].name) {
-			audit_log_format(ab, " name=");
+			audit_log_format(ab, "name=");
 			audit_log_untrustedstring(ab, context->names[i].name);
 		}
 		audit_log_format(ab, " flags=%x\n", context->names[i].flags);
@@ -1272,3 +1323,63 @@ void audit_signal_info(int sig, struct t
 	}
 }
 
+#ifdef CONFIG_AUDITFILESYSTEM
+extern spinlock_t auditfs_lock;
+
+/* This has to be here instead of in auditfs.c, because it needs to
+   see the audit context */
+void auditfs_attach_wdata(struct watch *w, const void *name,
+ 			  struct inode *inode, uint action)
+{
+	int disabled;
+	struct audit_context *ctx = current->audit_context;
+	struct audit_aux_data_watched *ax;
+	struct audit_watch_info *this, *winfo;
+	struct hlist_node *pos, *tmp;
+
+	printk("BEEP!\n");
+
+	if (!ctx || (w->w_user != FSW_USER_AUDIT))
+		return;
+
+	ax = kmalloc(sizeof(*ax), GFP_KERNEL);
+	if (!ax)
+		return;
+
+	disabled = audit_filter_syscall(current, ctx,
+					&audit_filter_list[AUDIT_FILTER_WATCH]);
+	if (ctx->in_syscall && !ctx->auditable && AUDIT_DISABLED != disabled)
+		 ctx->auditable = 1;
+
+	INIT_HLIST_HEAD(&ax->watches);
+
+	winfo = kmalloc(sizeof(struct audit_watch_info), GFP_KERNEL);
+	if (!winfo)
+		goto auditfs_attach_wdata_fail;
+	winfo->watch = fswatch_get_watch(w);
+	winfo->action = action;
+	hlist_add_head(&winfo->node, &ax->watches);
+
+	ax->mode = inode->i_mode;
+	ax->ino = inode->i_ino;
+	ax->uid = inode->i_uid;
+	ax->gid = inode->i_gid;
+	ax->dev = inode->i_sb->s_dev;
+	ax->rdev = inode->i_rdev;
+
+	ax->link.type = AUDIT_FS_INODE;
+	ax->link.next = ctx->aux;
+	ctx->aux = (void *)ax;
+
+	return;
+
+auditfs_attach_wdata_fail:
+	hlist_for_each_entry_safe(this, pos, tmp, &ax->watches, node) {
+		hlist_del(&this->node);
+		fswatch_put_watch(this->watch);
+		kfree(this);
+	}
+	kfree(ax);
+}
+
+#endif /* CONFIG_AUDITFILESYSTEM */
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -100,6 +100,9 @@ static struct nlmsg_perm nlmsg_audit_per
 	{ AUDIT_DEL,		NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
 	{ AUDIT_USER,		NETLINK_AUDIT_SOCKET__NLMSG_RELAY    },
 	{ AUDIT_SIGNAL_INFO,	NETLINK_AUDIT_SOCKET__NLMSG_READ     },
+	{ AUDIT_WATCH_INS,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
+	{ AUDIT_WATCH_REM,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
+	{ AUDIT_WATCH_LIST,	NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
 };
 
 




More information about the Linux-audit mailing list