#ifndef TEST_FCAP_C

# include <linux/kernel.h>
# include <linux/module.h>

# include <linux/fs.h>
# include <linux/sched.h>

#endif /* ndef TEST_FCAP_C */

/* TODO: need a spinlock to protect changes to the chain etc. */
/* TODO: need to think about liberating the allocated memory */

/* the non-hashed fields are a simple effort to catch when the file
   has changed -- without this module being notified. Cases of this
   should be removed from the kernel, since this scheme is not
   infallible */

struct file_index {

    /* linked list */
    struct file_index *next;

    /* reference */
    unsigned long fi_ino;
    kdev_t fi_dev;

    /* HACK: clues to verify that inode has not changed */

    umode_t fi_mode;
    uid_t fi_uid;
    gid_t fi_gid;
    time_t fi_mtime;
    __u32 fi_generation;

    /* stored capabilities */

    kernel_cap_t fi_effective;
    kernel_cap_t fi_inheritable;
    kernel_cap_t fi_permitted;
};

#define FI_BINS           37
#define FI_BLKS           10

static struct file_index **fi_tab = NULL;
static struct file_index *chain = NULL;

/* what is up with this not working? */

static void l_memset(void *p, int x, int length)
{
    unsigned char *u = p;
    while (length-- > 0) {
	*(u++) = x;
    }
}

/* linked list allocation/removal */

static struct file_index *alloc_fi(void)
{
    struct file_index *remains;

    if (chain == NULL) {
	int i;

	chain = (struct file_index *) vmalloc(sizeof(struct file_index)
					      * FI_BLKS);
	l_memset(chain, 0, FI_BLKS * sizeof(*chain));

	for (i=0; ++i<FI_BLKS; chain[i-1].next = (chain+i));
    }

    remains = chain;
    chain = chain->next;
    remains->next = NULL;

    return remains;
}

static struct file_index *free_fi(struct file_index *fi)
{
    struct file_index *remains;

    /* clean up */
    remains = fi->next;
    l_memset(fi, 0, sizeof(*fi));
    fi->next = chain;
    chain = fi;

    return remains;
}

#define hash_fn(inode, dev)  (( (inode) - (dev) ) % FI_BINS)

/* hash array maintenance */

static struct file_index *locate_fi(struct inode *inode,
				    struct file_index ***fi_prev_p)
{
    struct file_index *fi, **fi_p;

    *fi_prev_p = &fi_tab[hash_fn(inode->i_ino, inode->i_dev)];

    for (fi = **fi_prev_p; fi ; fi = fi->next) {
	if ((fi->fi_ino == inode->i_ino)
	    && (fi->fi_dev == inode->i_dev)) {
	    return fi;
	}
	*fi_prev_p = &fi->next;
    }

    return NULL;
}

static void drop_fi(struct inode *inode)
{
    struct file_index *fi, **fi_ptr;

    fi = locate_fi(inode, &fi_ptr);
    if (fi) {
	*fi_ptr = free_fi(fi);
    }
}

static struct file_index *find_fi(struct inode *inode)
{
    struct file_index *fi, **fi_ptr;

    /* just in case we have multiple entries -- a bug! */
    while ((fi = locate_fi(inode, &fi_ptr))) {
	if ((inode->i_mode == fi->fi_mode)
	    && (inode->i_uid == fi->fi_uid)
	    && (inode->i_gid == fi->fi_gid)
	    && (inode->i_mtime == fi->fi_mtime)
	    && (inode->i_generation == fi->fi_generation)) {

	    break;
	}

	*fi_ptr = free_fi(fi);
    }

    return fi;
}

static int add_fi(struct inode *inode, kernel_cap_t *effective,
		  kernel_cap_t *inheritable, kernel_cap_t *permitted)
{
    int replacement = 0;
    struct file_index *fi;

    if ((fi = find_fi(inode)) == NULL) {
	fi = alloc_fi();
	if (fi == NULL) {
	    printk("out of memory for new capability set");
	    return -ENOMEM;
	}
    } else {
	replacement = 1;
    }

    fi->fi_ino = inode->i_ino;
    fi->fi_dev = inode->i_dev;

    fi->fi_mode = inode->i_mode;
    fi->fi_uid = inode->i_uid;
    fi->fi_gid = inode->i_gid;
    fi->fi_mtime = inode->i_mtime;
    fi->fi_generation = inode->i_generation;

    fi->fi_effective = *effective;
    fi->fi_inheritable = *inheritable;
    fi->fi_permitted = *permitted;
    
    if (!replacement) {
	int h = hash_fn(fi->fi_ino, fi->fi_dev);
	fi->next = fi_tab[h];
	fi_tab[h] = fi;
    }

    return 0;
}

/*
 * main callback for identifying capabilities
 */

int static cap_select_by_dentry(struct dentry *dentry, int operation,
				kernel_cap_t *effective,
				kernel_cap_t *inheritable,
				kernel_cap_t *permitted)
{
    if (IS_ERR(dentry->d_inode)) {
	return -EINVAL;
    }

    /* need to call fns based on operation */
    switch (operation) {
    case _CAP_FS_SET:
    {
	if (effective && inheritable && permitted) {
	    return add_fi(dentry->d_inode, effective, inheritable, permitted);
	} else {
	    drop_fi(dentry->d_inode);
	    return 0;
	}
    }
    case _CAP_FS_GET:
    {
	struct file_index *fi;
	kernel_cap_t inh,eff,per;

	fi = find_fi(dentry->d_inode);
	if (fi) {
	    *effective = fi->fi_effective;
	    *inheritable = fi->fi_inheritable;
	    *permitted = fi->fi_permitted;
	}

	return 0;
    }
    default:
	printk("invalid capability call");
	return -EINVAL;
    }
}

/*
 * Initialize module
 */

int init_module()
{
    /* If we return a non zero value, it means that init_module failed and
     * the kernel module can't be loaded */

    fi_tab = (struct file_index **) vmalloc(sizeof(struct file_index *)
					    * FI_BINS);
    if (fi_tab == NULL) {
	printk("failed to allocate a hash header cache");
	return 1;
    }
    l_memset(fi_tab, 0, sizeof(struct file_index *) * FI_BINS);

    return !fs_capability_fn_register(cap_select_by_dentry);
}


/* Dropping the module.  This is pretty bad. The kernel is likely to
   crash if the kernel was not compiled with debugging enabled.. */

void cleanup_module()
{
    printk("Ouch. You really are on your own now\n");
    (void) fs_capability_fn_register(NULL);
}
