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