#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>
#include <linux/config.h>
/*
 * Originally by Anonymous (as far as I know...)
 * Linux version by Bas Laarhoven <bas@vimec.nl>
 * 0.99.14 version by Jon Tombs <jon@gtex02.us.es>,
 *
 * Heavily modified by Bjorn Ekwall <bj0rn@blox.se> May 1994 (C)
 * This source is covered by the GNU GPL, the same as all kernel sources.
 *
 * Features:
 *	- Supports stacked modules (removable only of there are no dependents).
 *	- Supports table of symbols defined by the modules.
 *	- Supports /proc/ksyms, showing value, name and owner of all
 *	  the symbols defined by all modules (in stack order).
 *	- Added module dependencies information into /proc/modules
 *	- Supports redefines of all symbols, for streams-like behaviour.
 *	- Compatible with older versions of insmod.
 *
 * New addition in December 1994: (Bjorn Ekwall, idea from Jacques Gelinas)
 *	- Externally callable function:
 *
 *		"int register_symtab(struct symbol_table *)"
 *
 *	  This function can be called from within the kernel,
 *	  and ALSO from loadable modules.
 *	  The goal is to assist in modularizing the kernel even more,
 *	  and finally: reducing the number of entries in ksyms.c
 *	  since every subsystem should now be able to decide and
 *	  control exactly what symbols it wants to export, locally!
 *
 * On 1-Aug-95:  <Matti.Aarnio@utu.fi>  altered code to use same style as
 *		 do  /proc/net/XXX  "files".  Namely allow more than 4kB
 *		 (or what the block size if) output.
 *
 *	- Use dummy syscall functions for users who disable all
 *	  module support. Similar to kernel/sys.c (Paul Gortmaker)
 */

#ifdef CONFIG_MODULES		/* a *big* #ifdef block... */

#ifdef DEBUG_MODULE
#define PRINTK(a) printk a
#else
#define PRINTK(a) /* */
#endif

static struct module kernel_module;
static struct module *module_list = &kernel_module;

static int freeing_modules; /* true if some modules are marked for deletion */

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

static int module_init_flag = 0; /* Hmm... */

extern struct symbol_table symbol_table; /* in kernel/ksyms.c */

/*
 * Called at boot time
 */
void init_modules(void) {
	struct internal_symbol *sym;
	int i;

	for (i = 0, sym = symbol_table.symbol; sym->name; ++sym, ++i)
		;
	symbol_table.n_symbols = i;

	kernel_module.symtab = &symbol_table;
	kernel_module.state = MOD_RUNNING; /* Hah! */
	kernel_module.name = "";
}

int
rename_module_symbol(char *old_name, char *new_name)
{
	struct internal_symbol *sym;
	int i = 0; /* keep gcc silent */

	if (module_list->symtab) {
		sym = module_list->symtab->symbol;
		for (i = module_list->symtab->n_symbols; i > 0; ++sym, --i) {
			if (strcmp(sym->name, old_name) == 0) { /* found it! */
				sym->name = new_name; /* done! */
				PRINTK(("renamed %s to %s\n", old_name, new_name));
				return 1; /* it worked! */
			}
		}
	}
	printk("rename %s to %s failed!\n", old_name, new_name);
	return 0; /* not there... */

	/*
	 * This one will change the name of the first matching symbol!
	 *
	 * With this function, you can replace the name of a symbol defined
	 * in the current module with a new name, e.g. when you want to insert
	 * your own function instead of a previously defined function
	 * with the same name.
	 *
	 * "Normal" usage:
	 *
	 * bogus_function(int params)
	 * {
	 *	do something "smart";
	 *	return real_function(params);
	 * }
	 *
	 * ...
	 *
	 * init_module()
	 * {
	 *	if (rename_module_symbol("_bogus_function", "_real_function"))
	 *		printk("yep!\n");
	 *	else
	 *		printk("no way!\n");
	 * ...
	 * }
	 *
	 * When loading this module, real_function will be resolved
	 * to the real function address.
	 * All later loaded modules that refer to "real_function()" will
	 * then really call "bogus_function()" instead!!!
	 *
	 * This feature will give you ample opportunities to get to know
	 * the taste of your foot when you stuff it into your mouth!!!
	 */
}

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

	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;
	}

	if ((mp = (struct module*) kmalloc(sspace, GFP_KERNEL)) == NULL) {
		return -ENOMEM;
	}
	strcpy((char *)(mp + 1), name); /* why not? */

	npages = (size + sizeof (long) + PAGE_SIZE - 1) / PAGE_SIZE;
	if ((addr = vmalloc(npages * PAGE_SIZE)) == 0) {
		kfree_s(mp, sspace);
		return -ENOMEM;
	}

	mp->next = module_list;
	mp->ref = NULL;
	mp->symtab = NULL;
	mp->name = (char *)(mp + 1);
	mp->size = npages;
	mp->addr = addr;
	mp->state = MOD_UNINITIALIZED;
	mp->cleanup = NULL;

	* (long *) addr = 0;	/* set use count to zero */
	module_list = mp;	/* link it in */

	PRINTK(("module `%s' (%lu pages @ 0x%08lx) created\n",
		mp->name, (unsigned long) mp->size, (unsigned long) mp->addr));
	return (unsigned long) addr;
}

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

	if (!suser())
		return -EPERM;

#ifdef __i386__
	/* A little bit of protection... we "know" where the user stack is... */

	if (symtab && ((unsigned long)symtab > 0xb0000000)) {
		printk("warning: you are using an old insmod, no symbols will be inserted!\n");
		symtab = NULL;
	}
#endif

	/*
	 * 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 (long) + PAGE_SIZE - 1) / PAGE_SIZE > mp->size)
		return -EINVAL;
	memcpy_fromfs((char *)mp->addr + sizeof (long), code, codesize);
	memset((char *)mp->addr + sizeof (long) + codesize, 0,
		mp->size * PAGE_SIZE - (codesize + sizeof (long)));
	PRINTK(( "module init entry = 0x%08lx, cleanup entry = 0x%08lx\n",
		(unsigned long) rt.init, (unsigned long) rt.cleanup));
	mp->cleanup = rt.cleanup;

	/* update kernel symbol table */
	if (symtab) { /* symtab == NULL means no new entries to handle */
		struct internal_symbol *sym;
		struct module_ref *ref;
		int size;
		int i;
		int legal_start;

		if ((error = verify_area(VERIFY_READ, &symtab->size, sizeof(symtab->size))))
			return error;
		size = get_user(&symtab->size);

		if ((newtab = (struct symbol_table*) kmalloc(size, GFP_KERNEL)) == NULL) {
			return -ENOMEM;
		}

		if ((error = verify_area(VERIFY_READ, symtab, size))) {
			kfree_s(newtab, size);
			return error;
		}
		memcpy_fromfs((char *)(newtab), symtab, size);

		/* sanity check */
		legal_start = sizeof(struct symbol_table) +
			newtab->n_symbols * sizeof(struct internal_symbol) +
			newtab->n_refs * sizeof(struct module_ref);

		if ((newtab->n_symbols < 0) || (newtab->n_refs < 0) || (legal_start > size)) {
			printk("Rejecting illegal symbol table (n_symbols=%d,n_refs=%d)\n",
			       newtab->n_symbols, newtab->n_refs);
			kfree_s(newtab, size);
			return -EINVAL;
		}

		/* relocate name pointers, index referred from start of table */
		for (sym = &(newtab->symbol[0]), i = 0; i < newtab->n_symbols; ++sym, ++i) {
			if ((unsigned long)sym->name < legal_start || size <= (unsigned long)sym->name) {
				printk("Rejecting illegal symbol table\n");
				kfree_s(newtab, size);
				return -EINVAL;
			}
			/* else */
			sym->name += (long)newtab;
		}
		mp->symtab = newtab;

		/* Update module references.
		 * On entry, from "insmod", ref->module points to
		 * the referenced module!
		 * Now it will point to the current module instead!
		 * The ref structure becomes the first link in the linked
		 * list of references to the referenced module.
		 * Also, "sym" from above, points to the first ref entry!!!
		 */
		for (ref = (struct module_ref *)sym, i = 0;
			i < newtab->n_refs; ++ref, ++i) {

			/* Check for valid reference */
			struct module *link = module_list;
			while (link && (ref->module != link))
				link = link->next;

			if (link == (struct module *)0) {
				printk("Non-module reference! Rejected!\n");
				return -EINVAL;
			}

			ref->next = ref->module->ref;
			ref->module->ref = ref;
			ref->module = mp;
		}
	}

	module_init_flag = 1; /* Hmm... */
	if ((*rt.init)() != 0) {
		module_init_flag = 0; /* Hmm... */
		return -EBUSY;
	}
	module_init_flag = 0; /* Hmm... */
	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;
	/* else */
	if (module_name != NULL) {
		if ((error = get_mod_name(module_name, name)) != 0)
			return error;
		if ((mp = find_module(name)) == NULL)
			return -ENOENT;
		if ((mp->ref != NULL) || (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.
 *
 * Note that the transient module symbols are copied _first_,
 * in lifo order!!!
 *
 * The symbols to "insmod" are according to the "old" format: struct kernel_sym,
 * which is actually quite handy for this purpose.
 * Note that insmod inserts a struct symbol_table later on...
 * (as that format is quite handy for the kernel...)
 *
 * For every module, the first (pseudo)symbol copied is the module name
 * and the address of the module struct.
 * This lets "insmod" keep track of references, and build the array of
 * struct module_refs in the symbol table.
 * The format of the module name is "#module", so that "insmod" can easily
 * notice when a module name comes along. Also, this will make it possible
 * to use old versions of "insmod", albeit with reduced functionality...
 * The "kernel" module has an empty name.
 */
asmlinkage int
sys_get_kernel_syms(struct kernel_sym *table)
{
	struct internal_symbol *from;
	struct kernel_sym isym;
	struct kernel_sym *to;
	struct module *mp = module_list;
	int i;
	int nmodsyms = 0;

	for (mp = module_list; mp; mp = mp->next) {
		if (mp->symtab && mp->symtab->n_symbols) {
			/* include the count for the module name! */
			nmodsyms += mp->symtab->n_symbols + 1;
		}
		else
			/* include the count for the module name! */
			nmodsyms += 1; /* return modules without symbols too */
	}

	if (table != NULL) {
		to = table;

		if ((i = verify_area(VERIFY_WRITE, to, nmodsyms * sizeof(*table))))
			return i;

		/* copy all module symbols first (always LIFO order) */
		for (mp = module_list; mp; mp = mp->next) {
			if (mp->state == MOD_RUNNING) {
				/* magic: write module info as a pseudo symbol */
				isym.value = (unsigned long)mp;
				sprintf(isym.name, "#%s", mp->name);
				memcpy_tofs(to, &isym, sizeof isym);
				++to;

				if (mp->symtab != NULL) {
					for (i = mp->symtab->n_symbols,
						from = mp->symtab->symbol;
						i > 0; --i, ++from, ++to) {

						isym.value = (unsigned long)from->addr;
						strncpy(isym.name, from->name, sizeof isym.name);
						memcpy_tofs(to, &isym, sizeof isym);
					}
				}
			}
		}
	}

	return nmodsyms;
}


/*
 * 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_user(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;
}

static void
drop_refs(struct module *mp)
{
	struct module *step;
	struct module_ref *prev;
	struct module_ref *ref;

	for (step = module_list; step; step = step->next) {
		for (prev = ref = step->ref; ref; ref = prev->next) {
			if (ref->module == mp) {
				if (ref == step->ref)
					step->ref = ref->next;
				else
					prev->next = ref->next;
				break; /* every module only references once! */
			}
			else
				prev = ref;
		}
	}
}

/*
 * 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;
				if (mp->symtab) {
					if (mp->symtab->n_refs)
						drop_refs(mp);
					if (mp->symtab->size)
						kfree_s(mp->symtab, mp->symtab->size);
				}
				vfree(mp->addr);
				kfree_s(mp, sizeof(struct module) + MOD_MAX_NAME);
				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;
	const char *q;
	int i;
	struct module *mp;
	struct module_ref *ref;
	char size[32];

	p = buf;
	/* Do not show the kernel pseudo module */
	for (mp = module_list ; mp && mp->next; mp = mp->next) {
		if (p - buf > 4096 - 100)
			break;			/* avoid overflowing buffer */
		q = mp->name;
		if (*q == '\0' && mp->size == 0 && mp->ref == NULL)
			continue; /* don't list modules for kernel syms */
		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) {
			sprintf(size,"\t%ld",GET_USE_COUNT(mp));
			q=size;
		}
		else if (mp->state == MOD_DELETED)
			q = "  (deleted)";
		else
			q = "  (bad state)";
		while (*q)
			*p++ = *q++;

		if ((ref = mp->ref) != NULL) {
			*p++ = '\t';
			*p++ = '[';
			for (; ref; ref = ref->next) {
				q = ref->module->name;
				while (*q)
					*p++ = *q++;
				if (ref->next)
					*p++ = ' ';
			}
			*p++ = ']';
		}
		*p++ = '\n';
	}
	return p - buf;
}


/*
 * Called by the /proc file system to return a current list of ksyms.
 */
int get_ksyms_list(char *buf, char **start, off_t offset, int length)
{
	struct module *mp;
	struct internal_symbol *sym;
	int i;
	char *p = buf;
	int len     = 0;	/* code from  net/ipv4/proc.c */
	off_t pos   = 0;
	off_t begin = 0;

	for (mp = module_list; mp; mp = mp->next) {
		if ((mp->state == MOD_RUNNING) &&
		    (mp->symtab != NULL) &&
		    (mp->symtab->n_symbols > 0)) {
			for (i = mp->symtab->n_symbols,
				sym = mp->symtab->symbol;
				i > 0; --i, ++sym) {

				p = buf + len;
				if (mp->name[0]) {
					len += sprintf(p, "%08lx %s\t[%s]\n",
						       (long)sym->addr,
						       sym->name, mp->name);
				} else {
					len += sprintf(p, "%08lx %s\n",
						       (long)sym->addr,
						       sym->name);
				}
				pos = begin + len;
				if (pos < offset) {
					len   = 0;
					begin = pos;
				}
				pos = begin + len;
				if (pos > offset+length)
					goto leave_the_loop;
			}
		}
	}
    leave_the_loop:
	*start = buf + (offset - begin);
	len -= (offset - begin);
	if (len > length)
		len = length;
	return len;
}

/*
 * Rules:
 * - The new symbol table should be statically allocated, or else you _have_
 *   to set the "size" field of the struct to the number of bytes allocated.
 *
 * - The strings that name the symbols will not be copied, maybe the pointers
 *
 * - For a loadable module, the function should only be called in the
 *   context of init_module
 *
 * Those are the only restrictions! (apart from not being reentrant...)
 *
 * If you want to remove a symbol table for a loadable module,
 * the call looks like: "register_symtab(0)".
 *
 * The look of the code is mostly dictated by the format of
 * the frozen struct symbol_table, due to compatibility demands.
 */
#define INTSIZ sizeof(struct internal_symbol)
#define REFSIZ sizeof(struct module_ref)
#define SYMSIZ sizeof(struct symbol_table)
#define MODSIZ sizeof(struct module)
static struct symbol_table nulltab;

int
register_symtab(struct symbol_table *intab)
{
	struct module *mp;
	struct module *link;
	struct symbol_table *oldtab;
	struct symbol_table *newtab;
	struct module_ref *newref;
	int size;

	if (intab && (intab->n_symbols == 0)) {
		struct internal_symbol *sym;
		/* How many symbols, really? */

		for (sym = intab->symbol; sym->name; ++sym)
			intab->n_symbols +=1;
	}

#if 1
	if (module_init_flag == 0) { /* Hmm... */
#else
	if (module_list == &kernel_module) {
#endif
		/* Aha! Called from an "internal" module */
		if (!intab)
			return 0; /* or -ESILLY_PROGRAMMER :-) */

		/* create a pseudo module! */
		if (!(mp = (struct module*) kmalloc(MODSIZ, GFP_KERNEL))) {
			/* panic time! */
			printk("Out of memory for new symbol table!\n");
			return -ENOMEM;
		}
		/* else  OK */
		memset(mp, 0, MODSIZ);
		mp->state = MOD_RUNNING; /* Since it is resident... */
		mp->name = ""; /* This is still the "kernel" symbol table! */
		mp->symtab = intab;

		/* link it in _after_ the resident symbol table */
		mp->next = kernel_module.next;
		kernel_module.next = mp;

		return 0;
	}

	/* else ******** Called from a loadable module **********/

	/*
	 * This call should _only_ be done in the context of the
	 * call to  init_module  i.e. when loading the module!!
	 * Or else...
	 */
	mp = module_list; /* true when doing init_module! */

	/* Any table there before? */
	if ((oldtab = mp->symtab) == (struct symbol_table*)0) {
		/* No, just insert it! */
		mp->symtab = intab;
		return 0;
	}

	/* else  ****** we have to replace the module symbol table ******/
#if 0
	if (oldtab->n_symbols > 0) {
		/* Oh dear, I have to drop the old ones... */
		printk("Warning, dropping old symbols\n");
	}
#endif

	if (oldtab->n_refs == 0) { /* no problems! */
		mp->symtab = intab;
		/* if the old table was kmalloc-ed, drop it */
		if (oldtab->size > 0)
			kfree_s(oldtab, oldtab->size);

		return 0;
	}

	/* else */
	/***** The module references other modules... insmod said so! *****/
	/* We have to allocate a new symbol table, or we lose them! */
	if (intab == (struct symbol_table*)0)
		intab = &nulltab; /* easier code with zeroes in place */

	/* the input symbol table space does not include the string table */
	/* (it does for symbol tables that insmod creates) */

	if (!(newtab = (struct symbol_table*)kmalloc(
		size = SYMSIZ + intab->n_symbols * INTSIZ +
			oldtab->n_refs * REFSIZ,
		GFP_KERNEL))) {
		/* panic time! */
		printk("Out of memory for new symbol table!\n");
		return -ENOMEM;
	}

	/* copy up to, and including, the new symbols */
	memcpy(newtab, intab, SYMSIZ + intab->n_symbols * INTSIZ);

	newtab->size = size;
	newtab->n_refs = oldtab->n_refs;

	/* copy references */
	memcpy( ((char *)newtab) + SYMSIZ + intab->n_symbols * INTSIZ,
		((char *)oldtab) + SYMSIZ + oldtab->n_symbols * INTSIZ,
		oldtab->n_refs * REFSIZ);

	/* relink references from the old table to the new one */

	/* pointer to the first reference entry in newtab! Really! */
	newref = (struct module_ref*) &(newtab->symbol[newtab->n_symbols]);

	/* check for reference links from previous modules */
	for (	link = module_list;
		link && (link != &kernel_module);
		link = link->next) {

		if (link->ref && (link->ref->module == mp))
			link->ref = newref++;
	}

	mp->symtab = newtab;

	/* all references (if any) have been handled */

	/* if the old table was kmalloc-ed, drop it */
	if (oldtab->size > 0)
		kfree_s(oldtab, oldtab->size);

	return 0;
}

#else		/* CONFIG_MODULES */

/* Dummy syscalls for people who don't want modules */

asmlinkage unsigned long sys_create_module(void)
{
	return -ENOSYS;
}

asmlinkage int sys_init_module(void)
{
	return -ENOSYS;
}

asmlinkage int sys_delete_module(void)
{
	return -ENOSYS;
}

asmlinkage int sys_get_kernel_syms(void)
{
	return -ENOSYS;
}

int register_symtab(struct symbol_table *intab)
{
	return 0;
}

#endif	/* CONFIG_MODULES */

