#include "audit.h"
#include <linux/inotify.h>
#include <linux/namei.h>
#include <linux/mount.h>
struct audit_tree;
struct audit_chunk;
struct audit_tree {
atomic_t count;
int goner;
struct audit_chunk *root;
struct list_head chunks;
struct list_head rules;
struct list_head list;
struct list_head same_root;
struct rcu_head head;
char pathname[];
};
struct audit_chunk {
struct list_head hash;
struct inotify_watch watch;
struct list_head trees; /* with root here */
int dead;
int count;
struct rcu_head head;
struct node {
struct list_head list;
struct audit_tree *owner;
unsigned index; /* index; upper bit indicates 'will prune' */
} owners[];
};
static LIST_HEAD(tree_list);
static LIST_HEAD(prune_list);
/*
* One struct chunk is attached to each inode of interest.
* We replace struct chunk on tagging/untagging.
* Rules have pointer to struct audit_tree.
* Rules have struct list_head rlist forming a list of rules over
* the same tree.
* References to struct chunk are collected at audit_inode{,_child}()
* time and used in AUDIT_TREE rule matching.
* These references are dropped at the same time we are calling
* audit_free_names(), etc.
*
* Cyclic lists galore:
* tree.chunks anchors chunk.owners[].list hash_lock
* tree.rules anchors rule.rlist audit_filter_mutex
* chunk.trees anchors tree.same_root hash_lock
* chunk.hash is a hash with middle bits of watch.inode as
* a hash function. RCU, hash_lock
*
* tree is refcounted; one reference for "some rules on rules_list refer to
* it", one for each chunk with pointer to it.
*
* chunk is refcounted by embedded inotify_watch.
*
* node.index allows to get from node.list to containing chunk.
* MSB of that sucker is stolen to mark taggings that we might have to
* revert - several operations have very unpleasant cleanup logics and
* that makes a difference. Some.
*/
static struct inotify_handle *rtree_ih;
static struct audit_tree *alloc_tree(const char *s)
{
struct audit_tree *tree;
tree = kmalloc(sizeof(struct audit_tree) + strlen(s) + 1, GFP_KERNEL);
if (tree) {
atomic_set(&tree->count, 1);
tree->goner = 0;
INIT_LIST_HEAD(&tree->chunks);
INIT_LIST_HEAD(&tree->rules);
INIT_LIST_HEAD(&tree->list);
INIT_LIST_HEAD(&tree->same_root);
tree->root = NULL;
strcpy(tree->pathname, s);
}
return tree;
}
static inline void get_tree(struct audit_tree *tree)
{
atomic_inc(&tree->count);
}
static void __put_tree(struct rcu_head *rcu)
{
struct audit_tree *tree = container_of(rcu, struct audit_tree, head);
kfree(tree);
}
static inline void put_tree(struct audit_tree *tree)
{
if (atomic_dec_and_test(&tree->count))
call_rcu(&tree->head, __put_tree);
}
/* to avoid bringing the entire thing in audit.h */
const char *audit_tree_path(struct audit_tree *tree)
{
return tree->pathname;
}
static struct audit_chunk *alloc_chunk(int count)
{
struct audit_chunk *chunk;
size_t size;
int i;
size = offsetof(struct audit_chunk, owners) + count * sizeof(struct node);
chunk = kzalloc(size, GFP_KERNEL);
if (!chunk)
return NULL;
INIT_LIST_HEAD(&chunk->hash);
INIT_LIST_HEAD(&chunk->trees);
chunk->count = count;
for (i = 0; i < count; i++) {
INIT_LIST_HEAD(&chunk->owners[i].list);
chunk->owners[i].index = i;
}
inotify_init_watch(&chunk->watch);
return chunk;
}
static void __free_chunk(struct rcu_head *rcu)
{
struct audit_chunk *chunk = container_of(rcu, struct audit_chunk, head);
int i;
for (i = 0; i < chunk->count; i++) {
if (chunk->owners[i].owner)
put_tree(chunk->owners[i].owner);
}
kfree(chunk);
}
static inline void free_chunk(struct audit_chunk *chunk)
{
call_rcu(&chunk->head, __free_chunk);
}
void audit_put_chunk(struct audit_chunk *chunk)
{
put_inotify_watch(&chunk->watch);
}
enum {HASH_SIZE = 128};
static struct list_head chunk_hash_heads[HASH_SIZE];
static __cacheline_aligned_in_smp DEFINE_SPINLOCK(hash_lock);
static inline struct list_head *chunk_hash(const struct inode *inode)
{
unsigned long n = (unsigned long)inode / L1_CACHE_BYTES;
return chunk_hash_heads + n % HASH_SIZE;
}
/* hash_lock is held by caller */
static void insert_hash(struct audit_chunk *chunk)
{
struct list_head *list = chunk_hash(chunk->watch.inode);
list_add_rcu(&chunk->hash, list);
}
/* called under rcu_read_lock */
struct audit_chunk *audit_tree_lookup(const struct inode *inode)
{
struct list_head *list = chunk_hash