splinter work on generic fs notification framework

Chris Wright chrisw at osdl.org
Tue Jul 12 20:13:08 UTC 2005


* Timothy R. Chavez (tinytim at us.ibm.com) wrote:
> Based on recent discussion on LKML and some IRC conversations, a generic file 
> system notification framework is being written to merge common functionality 
> between the "auditfs" component of the audit framework and Inotify.  This 
> framework will be mostly comprised of the watch logic implemented by the 
> "auditfs" component (I_AUDIT, hash table, update hook, helper functions, etc) 
> and the fsnotify hooks written by Robert Love.  User's of this framework will 
> pass their own callback functions in when adding a watch via this framework 
> which will effectively be stored on the watch and called from the watch hook 
> that discovers it.
> 
> We introduce fs/watch.c and include/linux/watch.h
> 
> Some of the oustanding issues:
> 
> o The fsnotify hooks will need to be expanded to cover more of the file system 
> to fufill CAPP's needs.

Specifics?  I assume you mean move and permission?  Ah, I see, you
didn't hook audit into it yet, because there's a couple interfaces that
need dentry rather than d_name.name.

> o The way we did "movement" notifications is different how Inotify did them.  
> The trick is to do it such that its mutually beneficial.

inotify won't be able to use the audit style hook without some changes.
that stuff is called with dcache_lock held, and looks like inotify will
want to do some potentially blocking work to queue the event.

> o The interface for adding and removing watches should be the same for every 
> user.  I think that the audit_receive_watch|audit_insert/remove_watch bit was 
> quite slopply done *pats self on back*, so I think this gives me a chance to 
> redeem myself and make something functional and slightly more sexy.
> 
> o Filtering.  Should the MAY_* stuff be scrapped... How important is it?  I 
> think it's a bit sloppy and contrived too.  It's a fine concept.  We'd 
> ideally like to mitigate throughput where possible, but I think the 
> 'ausearch' tool should work fine for looking only and what's "interesting" 
> and the netlink speed-up coupled with the backlogs work should help buffer 
> any impact introduced by the extra records.  Opinions?
> 
> o I'd like to place an extra field in the record that tells you exactly what 
> type of action it was... trying to deduce from system call is a bit... 
> cumbersome... might be nice to have in the log, "action=MODIFIED" or 
> something.

The audit record you mean?  The watch callback has the context already.

> Feel free to add something..

Some comments sprinkled below:

> diff --exclude=.git -Nurp audit-2.6/fs/dcache.c audit-2.6.git-fsnotify/fs/dcache.c
> --- audit-2.6/fs/dcache.c	2005-07-10 10:26:50.000000000 -0500
> +++ audit-2.6.git-fsnotify/fs/dcache.c	2005-07-11 01:32:42.000000000 -0500
> @@ -32,6 +32,7 @@
>  #include <linux/seqlock.h>
>  #include <linux/swap.h>
>  #include <linux/bootmem.h>
> +#include <linux/watch.h>
>  
>  /* #define DCACHE_DEBUG 1 */
>  
> @@ -97,6 +98,7 @@ static inline void dentry_iput(struct de
>  {
>  	struct inode *inode = dentry->d_inode;
>  	if (inode) {
> +		watch_update(dentry, 1);

I'm not that fond of the rm flag.  And it may help to separate for
inotify.

>  		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;
> +	watch_update(entry, 0);
>  	spin_unlock(&dcache_lock);
>  	security_d_instantiate(entry, inode);
>  }
> @@ -978,6 +981,7 @@ struct dentry *d_splice_alias(struct ino
>  		new = __d_find_alias(inode, 1);
>  		if (new) {
>  			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
> +			watch_update(new, 0);

I think this is unneeded, it's picked up in d_move.

>  			spin_unlock(&dcache_lock);
>  			security_d_instantiate(new, inode);
>  			d_rehash(dentry);
> @@ -987,6 +991,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;
> +			watch_update(dentry, 0);
>  			spin_unlock(&dcache_lock);
>  			security_d_instantiate(dentry, inode);
>  			d_rehash(dentry);
> @@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentry
>  		if (!d_unhashed(dentry)) {
>  			atomic_inc(&dentry->d_count);
>  			found = dentry;
> +			watch_update(dentry, 0);
>  		}
>  		spin_unlock(&dentry->d_lock);
>  		break;
> @@ -1299,6 +1305,8 @@ void d_move(struct dentry * dentry, stru
>  		spin_lock(&target->d_lock);
>  	}
>  
> +	watch_update(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 +1340,7 @@ already_unhashed:
>  		list_add(&target->d_child, &target->d_parent->d_subdirs);
>  	}
>  
> +	watch_update(dentry, 0);
>  	list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
>  	spin_unlock(&target->d_lock);
> diff --exclude=.git -Nurp audit-2.6/fs/namei.c audit-2.6.git-fsnotify/fs/namei.c
> --- audit-2.6/fs/namei.c	2005-07-10 10:26:50.000000000 -0500
> +++ audit-2.6.git-fsnotify/fs/namei.c	2005-07-11 00:06:05.000000000 -0500
> @@ -21,7 +21,7 @@
>  #include <linux/namei.h>
>  #include <linux/quotaops.h>
>  #include <linux/pagemap.h>
> -#include <linux/dnotify.h>
> +#include <linux/watch.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) {
> -		inode_dir_notify(dir, DN_CREATE);
> +		watch_notify_create(dir, dentry->d_name.name);

need dentry here, and the others as well

> @@ -2187,6 +2188,7 @@ 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;
> @@ -2208,18 +2210,18 @@ int vfs_rename(struct inode *old_dir, st
>  	DQUOT_INIT(old_dir);
>  	DQUOT_INIT(new_dir);
>  
> +	old_name = watch_notify_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;
> +		watch_notify_move(old_dir, new_dir, old_name, new_name, is_dir);

this is only for inotify move_from/move_to, and will need to be collapsed
into one way

>  	}
> +	watch_notify_oldname_free(old_name);
> +
>  	return error;
>  }
>  
> diff --exclude=.git -Nurp audit-2.6/fs/watch.c audit-2.6.git-fsnotify/fs/watch.c
> --- audit-2.6/fs/watch.c	1969-12-31 18:00:00.000000000 -0600
> +++ audit-2.6.git-fsnotify/fs/watch.c	2005-07-11 01:34:18.000000000 -0500
> @@ -0,0 +1,550 @@
> +#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/watch.h>
> +#include <linux/module.h>
> +#include <asm/uaccess.h>
> +
> +static kmem_cache_t *watch_cache;
> +
> +static spinlock_t watch_lock = SPIN_LOCK_UNLOCKED;
> +static spinlock_t watch_hash_lock = SPIN_LOCK_UNLOCKED;
> +extern spinlock_t inode_lock;
> +
> +static int watch_nr_watches;
> +static int watch_pool_size;
> +static struct watch_inode_data *watch_data_pool;
> +static struct watch_inode_data **watch_hash_table;
> +static int watch_hash_bits;
> +static int watch_cache_buckets = 16384;
> +module_param(watch_cache_buckets, int, 0);
> +MODULE_PARM_DESC(watch_cache_buckets,
> +		 "Number of watch cache entries to allocate (default 16384)\n");
> +
> +static void watch_data_put(struct watch_inode_data *data);
> +
> +static int watch_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 = watch_data_pool;
> +	watch_data_pool = new;
> +	watch_nr_watches++;
> +	watch_pool_size += 2;
> +	spin_unlock(&watch_hash_lock);
> +	return 0;
> +}
> +static void watch_data_pool_shrink(void)
> +{
> +	spin_lock(&watch_hash_lock);
> +	watch_nr_watches--;
> +
> +	while (watch_pool_size > watch_nr_watches + 1) {
> +		struct watch_inode_data *old = watch_data_pool;
> +		watch_data_pool = old->next_hash;
> +		watch_pool_size--;
> +		kfree(old);
> +	}
> +	spin_unlock(&watch_hash_lock);
> +}
> +
> +static struct watch_inode_data *watch_data_get(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, watch_hash_bits);
> +	list = &watch_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 = watch_data_pool;
> +		watch_data_pool = ret->next_hash;
> +		watch_pool_size--;

Is there any possibility this accounting could get out of sorts?
Same kind of preallocation would be needed for inotify move_from/move_to.
So maybe the update would need to be broken down...

> +
> +		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 *watch_fetch(const char *name,
> +				 struct watch_inode_data *data)
> +{
> +	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 = watch_get(watch);
> +			break;
> +		}
> +
> +	return ret;
> +}
> +
> +static inline struct watch *watch_fetch_lock(const char *name,
> +			       struct watch_inode_data *data)
> +{
> +	struct watch *ret = NULL;
> +
> +	if (name && data) {
> +		spin_lock(&watch_lock);
> +		ret = watch_fetch(name, data);
> +		spin_unlock(&watch_lock);
> +	}
> +
> +	return ret;
> +}
> +
> +inline struct watch *watch_alloc(void)
> +{
> +	struct watch *watch;
> +
> +	watch = kmem_cache_alloc(watch_cache, GFP_KERNEL);
> +	if (watch) {
> +		memset(watch, 0, sizeof(*watch));
> +		atomic_set(&watch->w_count, 1);
> +	}
> +
> +	return watch;
> +}
> +
> +void watch_free(struct watch *watch)
> +{
> +	if (watch) {
> +		kfree(watch->w_name);
> +		kfree(watch->w_path);
> +		kfree(watch->w_filterkey);
> +		BUG_ON(!hlist_unhashed(&watch->w_node));
> +		BUG_ON(!hlist_unhashed(&watch->w_master));
> +		BUG_ON(!hlist_unhashed(&watch->w_watched));
> +		kmem_cache_free(watch_cache, watch);
> +	}
> +}
> +
> +static inline void watch_destroy_watch(struct watch *watch)
> +{
> +	if (watch) {
> +		if (!hlist_unhashed(&watch->w_watched)) {
> +			hlist_del_init(&watch->w_watched);
> +			watch_put(watch);
> +		}
> +
> +		if (!hlist_unhashed(&watch->w_master)) {
> +			hlist_del_init(&watch->w_master);
> +			watch_put(watch);
> +		}
> +
> +		if (!hlist_unhashed(&watch->w_node)) {
> +			hlist_del_init(&watch->w_node);
> +			watch_put(watch);
> +		}
> +	}
> +}
> +
> +static inline void watch_drain_watchlist(struct watch_inode_data *data)
> +{
> +	struct watch *watch;
> +	struct hlist_node *pos, *tmp;
> +
> +	spin_lock(&watch_lock);
> +	hlist_for_each_entry_safe(watch, pos, tmp, &data->watchlist, w_node) {
> +		watch_destroy_watch(watch);
> +		watch_data_pool_shrink();
> +		/* Add notification that watch was removed implicitly */
> +	}
> +	spin_unlock(&watch_lock);
> +}
> +
> +static void watch_data_unhash(struct watch_inode_data *data)
> +{
> +	int h = hash_ptr(data->inode, watch_hash_bits);
> +	struct watch_inode_data **list = &watch_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 watch_data_put(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)
> +			watch_data_unhash(data);
> +		spin_unlock(&watch_hash_lock);
> +
> +		watch_drain_watchlist(data);
> +
> +		spin_lock(&watch_hash_lock);
> +		/* Check whether to free it or return it to the pool */
> +		if (watch_nr_watches > watch_pool_size) {
> +			data->next_hash = watch_data_pool;
> +			watch_data_pool = data;
> +			watch_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 watch_update(struct dentry *dentry, int remove)
> +{
> +	struct watch *this, *watch;
> +	struct watch_inode_data *data, *parent;
> +	struct hlist_node *pos, *tmp;
> +
> +	if (!dentry || !dentry->d_inode)
> +		return;
> +
> +	if (!dentry->d_parent || !dentry->d_parent->d_inode)
> +		return;
> +
> +	/* If there's no audit data on the parent inode, then there can
> +	   be no watches to add or remove */
> +	parent = watch_data_get(dentry->d_parent->d_inode, 0);
> +	if (!parent)
> +		return;
> +
> +	watch = watch_fetch_lock(dentry->d_name.name, parent);
> +
> +	/* Fetch audit data, using the preallocated one from the watch if
> +	   there is actually a relevant watch and the inode didn't already
> +	   have any audit data */
> +	data = watch_data_get(dentry->d_inode, !!watch);
> +
> +	/* If there's no data, then there wasn't a watch either.
> +	   Nothing to see here; move along */
> +	if (!data)
> +		goto put_watch;
> +
> +	spin_lock(&watch_lock);
> +	if (remove) {
> +		if (watch && !hlist_unhashed(&watch->w_watched)) {
> +			hlist_del_init(&watch->w_watched);
> +			watch_put(watch);
> +		}
> +	} else {
> +		hlist_for_each_entry_safe(this, pos, tmp, &data->watches, w_watched)
> +			if (hlist_unhashed(&this->w_node)) {
> +				hlist_del_init(&this->w_watched);
> +				watch_put(this);
> +			}
> +		if (watch && hlist_unhashed(&watch->w_watched)) {
> +			watch_get(watch);
> +			hlist_add_head(&watch->w_watched, &data->watches);
> +		}
> +	}
> +	spin_unlock(&watch_lock);
> +	watch_data_put(data);
> +
> + put_watch:
> +	watch_put(watch);
> +	watch_data_put(parent);
> +}
> +
> +int watch_add(struct watch *watch, struct inode *inode)
> +{
> +	int ret;
> +	struct nameidata nd;
> +	struct watch_inode_data *pdata;
> +	struct watch *lookup;
> +
> +	/* Grow the pool by two -- one for the watch itself, and
> +	   one for the parent directory */
> +	if (watch_data_pool_grow())
> +		return -ENOMEM;
> +
> +	ret = path_lookup(watch->w_path, LOOKUP_PARENT, &nd);
> +	if (ret < 0)
> +		goto out;
> +
> +	ret = -EPERM;
> +	if (nd.last_type != LAST_NORM || !nd.last.name)
> +		goto release;
> +
> +	pdata = watch_data_get(nd.dentry->d_inode, 1);
> +	if (!pdata)
> +		goto put_pdata;
> +
> +	ret = -EEXIST;
> +	lookup = watch_fetch_lock(nd.last.name, pdata);
> +	if (lookup) {
> +		watch_put(lookup);
> +		goto put_pdata;
> +	}
> +
> +	/* Insert */
> +
> +put_pdata:
> +	watch_data_put(pdata);
> +release:
> +	path_release(&nd);
> +out:
> +	if (ret)
> +		watch_data_pool_shrink();
> +	return ret;
> +}
> +
> +int watch_rem(struct watch *watch, struct inode *inode)
> +{
> +	return 0;
> +}
> +
> +struct watch *watch_get(struct watch *watch)
> +{
> +	int new;
> +
> +	if (watch) {
> +		new = atomic_inc_return(&watch->w_count);
> +		BUG_ON(new == 1);
> +	}
> +
> +	return watch;
> +}
> +
> +void watch_put(struct watch *watch)
> +{
> +	int new;
> +
> +	if (watch) {
> +		new = atomic_dec_return(&watch->w_count);
> +		if (!new)
> +			watch_free(watch);
> +	}
> +}
> +
> +void watch_inode_free(struct inode *inode)
> +{
> +	struct watch *watch;
> +	struct hlist_node *pos, *tmp;
> +	struct watch_inode_data *data = watch_data_get(inode, 0);
> +
> +	if (data) {
> +		spin_lock(&watch_hash_lock);
> +		watch_data_unhash(data);
> +		spin_unlock(&watch_hash_lock);
> +
> +		watch_drain_watchlist(data);
> +		/* Release all our references to any watches we may have on us */
> +		spin_lock(&watch_lock);
> +		hlist_for_each_entry_safe(watch, pos, tmp, &data->watches, w_watched) {
> +			hlist_del(&watch->w_watched);
> +                	watch_put(watch);
> +        	}
> +		spin_unlock(&watch_lock);
> +		watch_data_put(data);
> +	}
> +}
> +
> +int watch_init(void)
> +{
> +
> +	watch_cache = kmem_cache_create("watch_cache", sizeof(struct watch),
> +					0, 0, NULL, NULL);
> +	if (!watch_cache)
> +		goto watch_init_fail;
> +
> +	/* Set up hash table for inode objects */
> +	watch_hash_bits = long_log2(watch_cache_buckets);
> +	if (watch_cache_buckets != (1 << watch_hash_bits)) {
> +		watch_hash_bits++;
> +		watch_cache_buckets = 1 << watch_hash_bits;
> +		printk(KERN_NOTICE
> +		       "%s: watch_cache_buckets set to %d (bits %d)\n",
> +		       __FUNCTION__, watch_cache_buckets, watch_hash_bits);
> +	}
> +
> +	watch_hash_table = kmalloc(watch_cache_buckets * sizeof(void *), GFP_KERNEL);
> +
> +	if (!watch_hash_table) {
> +		printk(KERN_NOTICE "No memory to initialize watch cache.\n");
> +		goto watch_init_fail;
> +	}
> +
> +	memset(watch_hash_table, 0, watch_cache_buckets * sizeof(void *));
> +
> +	return 0;
> +
> +watch_init_fail:
> +	kmem_cache_destroy(watch_cache);
> +	return -ENOMEM;
> +}
> +

Heh, ok, I see, so nothing's using this yet ;-)

> +/*
> + * watch_notify_move - file old_name at old_dir was moved to new_name at new_dir
> + */
> +void watch_notify_move(struct inode *old_dir, struct inode *new_dir,
> +				 const char *old_name, const char *new_name,
> +				 int isdir)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_unlink - file was unlinked
> + */
> +void watch_notify_unlink(struct dentry *dentry, struct inode *dir)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_rmdir - directory was removed
> + */
> +void watch_notify_rmdir(struct dentry *dentry, struct inode *inode,
> +				  struct inode *dir)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_create - 'name' was linked in
> + */
> +void watch_notify_create(struct inode *inode, const char *name)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_mkdir - directory 'name' was created
> + */
> +void watch_notify_mkdir(struct inode *inode, const char *name)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_access - file was read
> + */
> +void watch_notify_access(struct dentry *dentry)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_modify - file was modified
> + */
> +void watch_notify_modify(struct dentry *dentry)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_open - file was opened
> + */
> +void watch_notify_open(struct dentry *dentry, int mask)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_close - file was closed
> + */
> +void watch_notify_close(struct file *file)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_xattr - extended attributes were changed
> + */
> +void watch_notify_xattr(struct dentry *dentry)
> +{
> +	return;
> +}
> +
> +/*
> + * watch_notify_change - notify_change event.  file was modified and/or metadata
> + * was changed.
> + */
> +void watch_notify_change(struct dentry *dentry, unsigned int ia_valid)
> +{
> +	return;
> +}
> +
> +void watch_notify_oldname_free(const char *old_name)
> +{
> +	return;
> +}
> +
> +const char *watch_notify_oldname_init(const char *name)
> +{
> +	return NULL;
> +}
> +
> diff --exclude=.git -Nurp audit-2.6/include/linux/audit.h audit-2.6.git-fsnotify/include/linux/audit.h
> --- audit-2.6/include/linux/audit.h	2005-07-10 10:26:50.000000000 -0500
> +++ audit-2.6.git-fsnotify/include/linux/audit.h	2005-07-11 00:51:06.000000000 -0500
> @@ -24,9 +24,15 @@
>  #ifndef _LINUX_AUDIT_H_
>  #define _LINUX_AUDIT_H_
>  
> +#ifdef __KERNEL__
>  #include <linux/sched.h>
>  #include <linux/elf.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
>   * 1100 - 1199 user space trusted application messages
> @@ -68,6 +74,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 +182,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 +205,29 @@ struct audit_rule {		/* for AUDIT_LIST, 
>  	__u32		values[AUDIT_MAX_FIELDS];
>  };
>  
> -#ifdef __KERNEL__
> +/* Structure to transport watch data to and from the kernel */
> +
> +struct watch_transport {
> +	__u32 dev_major;
> +	__u32 dev_minor;
> +	__u32 perms;
> +	__u32 valid;
> +	__u32 pathlen;
> +	__u32 fklen;
> +	char buf[0];
> +};

This is audit specific, right?  Shouldn't have generic watch name then.

> +#ifdef __KERNEL__
>  struct audit_sig_info {
>  	uid_t		uid;
>  	pid_t		pid;
>  };
>  
> +struct audit_watch_info {
> +	struct hlist_node node;
> +	struct watch *watch;
> +};
> +
>  struct audit_buffer;
>  struct audit_context;
>  struct inode;
> @@ -248,6 +274,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 +284,20 @@ extern int audit_filter_user(struct netl
>  #define audit_filter_user(cb,t) ({ 1; })
>  #endif
>  
> +#ifdef CONFIG_AUDITFILESYSTEM
> +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 audit_update_watch(struct dentry *dentry, int remove);
> +extern void auditfs_attach_wdata(struct inode *inode, struct hlist_head *watches,
> +				 int mask);
> +#else
> +#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 --exclude=.git -Nurp audit-2.6/include/linux/dnotify.h audit-2.6.git-fsnotify/include/linux/dnotify.h
> --- audit-2.6/include/linux/dnotify.h	2005-07-10 10:26:50.000000000 -0500
> +++ audit-2.6.git-fsnotify/include/linux/dnotify.h	2005-07-10 19:01:44.000000000 -0500
> @@ -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 --exclude=.git -Nurp audit-2.6/include/linux/watch.h audit-2.6.git-fsnotify/include/linux/watch.h
> --- audit-2.6/include/linux/watch.h	1969-12-31 18:00:00.000000000 -0600
> +++ audit-2.6.git-fsnotify/include/linux/watch.h	2005-07-11 01:33:45.000000000 -0500
> @@ -0,0 +1,57 @@
> +#ifndef _WATCH_H
> +#define _WATCH_H
> +
> +struct watch {
> +	atomic_t                w_count;
> +	struct hlist_node	w_node;		/* per-directory list         */
> +	struct hlist_node	w_master;	/* Master watch list          */
> +	struct hlist_node	w_watched;	/* Watches on inode           */
> +	dev_t			w_dev;		/* Superblock device          */
> +	__u32			w_perms;	/* Permissions filtering      */
> +	char			*w_name;	/* Watch beneath parent       */
> +	char			*w_path;	/* Insertion path             */
> +	char			*w_filterkey;	/* An arbitrary filtering key */
> +	void			(*w_func);	/* Callback function          */

Needs to be properly typed.

> +};
> +
> +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;	/* List of watches on inode   */
> +	struct hlist_head	watchlist;	/* Watches for children       */
> +};
> +
> +/* User */
> +extern int watch_add(struct watch *, struct inode *);
> +extern int watch_rem(struct watch *, struct inode *);
> +extern struct watch *watch_alloc(void);
> +extern struct watch *watch_get(struct watch *);
> +extern void watch_free(struct watch *);
> +extern void watch_put(struct watch *);

No need for extern.

> +/* Internals */
> +
> +extern int watch_init(void);
> +extern void watch_inode_free(struct inode *);
> +extern void watch_update(struct dentry *, int);
> +
> +/* Hooks */
> +#ifdef CONFIG_FSWATCH
> +extern const char *watch_notify_oldname_init(const char *);
> +extern void watch_notify_oldname_free(const char *);
> +extern void watch_notify_move(struct inode *, struct inode *, const char *, const char *, int);
> +extern void watch_notify_unlink(struct dentry *, struct inode *);
> +extern void watch_notify_rmdir(struct dentry *, struct inode *, struct inode *);
> +extern void watch_notify_create(struct inode *, const char *);
> +extern void watch_notify_mkdir(struct inode *, const char *);
> +extern void watch_notify_access(struct dentry *dentry);
> +extern void watch_notify_modify(struct dentry *dentry);
> +extern void watch_notify_open(struct dentry *dentry, int mask);
> +extern void watch_notify_close(struct file *file);
> +extern void watch_notify_xattr(struct dentry *dentry);
> +extern void watch_notify_change(struct dentry *dentry, unsigned int ia_valid);
> +#endif /* CONFIG_FSWATCH */
> +
> +#endif /* _WATCH_H */
> +
> diff --exclude=.git -Nurp audit-2.6/init/Kconfig audit-2.6.git-fsnotify/init/Kconfig
> --- audit-2.6/init/Kconfig	2005-07-10 10:26:50.000000000 -0500
> +++ audit-2.6.git-fsnotify/init/Kconfig	2005-07-10 19:01:48.000000000 -0500
> @@ -162,6 +162,12 @@ config SYSCTL
>  	  building a kernel for install/rescue disks or your system is very
>  	  limited in memory.
>  
> +config FSWATCH
> +	bool "Generic file watches"
> +	help
> +	  This is a generic framework for monitoring file system
> +	  activity.

fs/Kconfig material




More information about the Linux-audit mailing list