* Timothy R. Chavez (tinytim(a)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