#include <linux/errno.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <linux/mm.h>		/* defines GFP_KERNEL */
#include <linux/string.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/malloc.h>

struct module *module_list = NULL;
int freeing_modules;		/* true if some modules are marked for deletion */

struct module *find_module( const char *name);
int get_mod_name( char *user_name, char *buf);
int free_modules( void);

/*
 * Allocate space for a module.
 */
asmlinkage int
sys_create_module(char *module_name, unsigned long size)
{
	int npages;
	void* addr;
	int len;
	char name[MOD_MAX_NAME];
	char *savename;
	struct module *mp;
	int error;

	if (!suser())
		return -EPERM;
	if (module_name == NULL || size == 0)
		return -EINVAL;
	if ((error = get_mod_name(module_name, name)) != 0)
		return error;
	if (find_module(name) != NULL) {
		return -EEXIST;
	}
	len = strlen(name) + 1;
	if ((savename = (char*) kmalloc(len, GFP_KERNEL)) == NULL)
		return -ENOMEM;
	memcpy(savename, name, len);
	if ((mp = (struct module*) kmalloc(sizeof *mp, GFP_KERNEL)) == NULL) {
		kfree(savename);
		return -ENOMEM;
	}
	npages = (size + sizeof (int) + 4095) / 4096;
	if ((addr = vmalloc(npages * 4096)) == 0) {
		kfree_s(mp, sizeof *mp);
		kfree(savename);
		return -ENOMEM;
	}
	mp->name = savename;
	mp->size = npages;
	mp->addr = addr;
	mp->state = MOD_UNINITIALIZED;
	* (int *) addr = 0;		/* set use count to zero */
	mp->cleanup = NULL;
	mp->next = module_list;
	module_list = mp;
	printk("module `%s' (%lu pages @ 0x%08lx) created\n",
		mp->name, (unsigned long) mp->size, (unsigned long) mp->addr);
	return (int) addr;
}

/*
 * Initialize a module.
 */
asmlinkage int
sys_init_module(char *module_name, char *code, unsigned codesize,
		struct mod_routines *routines)
{
	struct module *mp;
	char name[MOD_MAX_NAME];
	int error;
	struct mod_routines rt;

	if (!suser())
		return -EPERM;
	/*
	 * First reclaim any memory from dead modules that where not
	 * freed when deleted. Should I think be done by timers when
	 * the module was deleted - Jon.
	 */
	free_modules();

	if ((error = get_mod_name(module_name, name)) != 0)
		return error;
	printk( "initializing module `%s', %d (0x%x) bytes\n",
		name, codesize, codesize);
	memcpy_fromfs(&rt, routines, sizeof rt);
	if ((mp = find_module(name)) == NULL)
		return -ENOENT;
	if ((codesize + sizeof (int) + 4095) / 4096 > mp->size)
		return -EINVAL;
	memcpy_fromfs((char *)mp->addr + sizeof (int), code, codesize);
	memset((char *)mp->addr + sizeof (int) + codesize, 0,
		mp->size * 4096 - (codesize + sizeof (int)));
	printk( "  init entry @ 0x%08lx, cleanup entry @ 0x%08lx\n",
		(unsigned long) rt.init, (unsigned long) rt.cleanup);
	mp->cleanup = rt.cleanup;
	if ((*rt.init)() != 0)
		return -EBUSY;
	mp->state = MOD_RUNNING;
	return 0;
}

asmlinkage int
sys_delete_module(char *module_name)
{
	struct module *mp;
	char name[MOD_MAX_NAME];
	int error;

	if (!suser())
		return -EPERM;
	if (module_name != NULL) {
		if ((error = get_mod_name(module_name, name)) != 0)
			return error;
		if ((mp = find_module(name)) == NULL)
			return -ENOENT;
		if (GET_USE_COUNT(mp) != 0)
			return -EBUSY;
		if (mp->state == MOD_RUNNING)
			(*mp->cleanup)();
		mp->state = MOD_DELETED;
	}
	free_modules();
	return 0;
}

/*
 * Copy the kernel symbol table to user space.  If the argument is null,
 * just return the size of the table.
 */
asmlinkage int
sys_get_kernel_syms(struct kernel_sym *table)
{
	struct symbol {
		unsigned long addr;
		char *name;
	};
	extern int symbol_table_size;
	extern struct symbol symbol_table[];
	int i;
	struct symbol *from;
	struct kernel_sym *to;
	struct kernel_sym sym;

	if (table != NULL) {
		from = symbol_table;
		to = table;
		i = verify_area(VERIFY_WRITE, to, symbol_table_size * sizeof *table);
		if (i)
			return i;
		for (i = symbol_table_size ; --i >= 0 ; ) {
			sym.value = from->addr;
			strncpy(sym.name, from->name, sizeof sym.name);
			memcpy_tofs(to, &sym, sizeof sym);
			from++, to++;
		}
	}
	return symbol_table_size;
}


/*
 * Copy the name of a module from user space.
 */
int
get_mod_name(char *user_name, char *buf)
{
	int i;

	i = 0;
	for (i = 0 ; (buf[i] = get_fs_byte(user_name + i)) != '\0' ; ) {
		if (++i >= MOD_MAX_NAME)
			return -E2BIG;
	}
	return 0;
}


/*
 * Look for a module by name, ignoring modules marked for deletion.
 */
struct module *
find_module( const char *name)
{
	struct module *mp;

	for (mp = module_list ; mp ; mp = mp->next) {
		if (mp->state == MOD_DELETED)
			continue;
		if (!strcmp(mp->name, name))
			break;
	}
	return mp;
}


/*
 * Try to free modules which have been marked for deletion.  Returns nonzero
 * if a module was actually freed.
 */
int
free_modules( void)
{
	struct module *mp;
	struct module **mpp;
	int did_deletion;

	did_deletion = 0;
	freeing_modules = 0;
	mpp = &module_list;
	while ((mp = *mpp) != NULL) {
		if (mp->state != MOD_DELETED) {
			mpp = &mp->next;
		} else if (GET_USE_COUNT(mp) != 0) {
			freeing_modules = 1;
			mpp = &mp->next;
		} else {	/* delete it */
			*mpp = mp->next;
			vfree(mp->addr);
			kfree(mp->name);
			kfree_s(mp, sizeof *mp);
			did_deletion = 1;
		}
	}
	return did_deletion;
}


/*
 * Called by the /proc file system to return a current list of modules.
 */
int get_module_list(char *buf)
{
	char *p;
	char *q;
	int i;
	struct module *mp;
	char size[32];

	p = buf;
	for (mp = module_list ; mp ; mp = mp->next) {
		if (p - buf > 4096 - 100)
			break;			/* avoid overflowing buffer */
		q = mp->name;
		i = 20;
		while (*q) {
			*p++ = *q++;
			i--;
		}
		sprintf(size, "%d", mp->size);
		i -= strlen(size);
		if (i <= 0)
			i = 1;
		while (--i >= 0)
			*p++ = ' ';
		q = size;
		while (*q)
			*p++ = *q++;
		if (mp->state == MOD_UNINITIALIZED)
			q = "  (uninitialized)";
		else if (mp->state == MOD_RUNNING)
			q = "";
		else if (mp->state == MOD_DELETED)
			q = "  (deleted)";
		else
			q = "  (bad state)";
		while (*q)
			*p++ = *q++;
		*p++ = '\n';
	}
	return p - buf;
}
