/*
 * Install a module in the kernel.
 *
 * See the file COPYING for your rights (GNU GPL)
 *
 * Originally by Anonymous (as far as I know...)
 * Linux version by Bas Laarhoven <bas@vimec.nl>
 * Modified by Jon Tombs.
 *
 * Support for transient and resident symbols
 * added by Bjorn Ekwall <bj0rn@blox.se> in June 1994 (C)
 *
 * Load map option conceived by Derek Atkins <warlord@MIT.EDU>
 *
 * Support for versioned kernels and symbols: Bjorn Ekwall in December 1994
 *
 * Merged in ksyms and rmmod in December 1994: Bjorn Ekwall
 *
 * Support for ELF modules: Bjorn Ekwall in December 1994 after having
 *                          mangled sources from, and been enlightened
 *                          and supported by Eric Youngdale <eric@aib.com>
 *                          (the kludges are all mine, don't blame Eric...)
 *
 * Support for array initializers: Bjorn Ekwall in January 1995
 * Support for string initializers: Bjorn Ekwall in January 1995
 * Fixed major bug in a.out bss variable handling: March '95, Bas.
 * ELF fixes from H.J.Lu <hjl@nynexst.com>
 * Many ELF and other fixes from:
 * 	James Bottomley <J.E.J.Bottomley@damtp.cambridge.ac.uk>
 * Full support for MODPATH setting: Henrik Storner <storner@osiris.ping.dk>
 * Removed limitation of unversioned module vs. versioned kernel: Bjorn
 * Handle all combinations of ELF vs a.out kernels/modules: Bjorn
 * Added syslog error reporting with option "-s": Jacques Gelinas
 * Added MOD_AUTOCLEAN and option "-k" (for kerneld"): Bjorn and Jacques
 * mc68000: Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
 * More fixes (ELF SHN_UNDEF et al) by  Andreas Schwab in February 1996
 * Added Sparc support: Eddie C. Dost <ecd@skynet.be>
 * Various improvements added by Hans Lermen <lermen@elserv.ffm.fgan.de>
 */

#ifndef MOD_AUTOCLEAN /* defined in <linux/module.h> by the "kerneld"-patch */
#define MOD_AUTOCLEAN 0x40000000 /* big enough, but no sign problems... */
#endif

static char *default_path[] = {
	".", "/linux/modules",
	"/lib/modules/%s/fs",
	"/lib/modules/%s/net",
	"/lib/modules/%s/scsi",
	"/lib/modules/%s/block",
	"/lib/modules/%s/cdrom",
	"/lib/modules/%s/ipv4",
	"/lib/modules/%s/misc",
	"/lib/modules/default/fs",
	"/lib/modules/default/net",
	"/lib/modules/default/scsi",
	"/lib/modules/default/block",
	"/lib/modules/default/cdrom",
	"/lib/modules/default/ipv4",
	"/lib/modules/default/misc",
	"/lib/modules/fs",
	"/lib/modules/net",
	"/lib/modules/scsi",
	"/lib/modules/block",
	"/lib/modules/cdrom",
	"/lib/modules/ipv4",
	"/lib/modules/misc",
	0
};

#include "insmod.h"
#include "../Version.h"
#define is_global(sp) (aout_flag?(sp->u.n.n_type & N_EXT) : \
				(ELF32_ST_BIND(sp->u.e.st_info) != STB_LOCAL))
#define is_undef(sp) (aout_flag?((sp->u.n.n_type & ~N_EXT) == N_UNDF) : \
			    (sp->u.e.st_shndx == SHN_UNDEF))

/* hack: sizeof(struct elfhdr) > sizeof(struct exec) */
Elf32_Ehdr header;
int aout_flag; /* to know the format of the module */
int elf_kernel = 0; /* to know the format of the kernel */

size_t codesize;
size_t progsize;
size_t bss1size;
size_t bss2size;

char *textseg;

int nsymbols;
struct symbol *symtab;
char *stringtab;
unsigned long addr;
int verbose = 0;
int silent_poll = 0;

static int export_flag = 1; /* See comment at option handler in main() */
static int force_load = 0;
static int loadmap = 0;
static int versioned_kernel = 0;
static struct kernel_sym nullsym;
static struct symbol *symroot;
static struct utsname uts_info;
static char kd_dbmfile[40];

void * ckalloc(size_t nbytes)
{
	void *p;

	if ((p = malloc(nbytes)) == NULL) {
	        perror("insmod: malloc failed");
		exit(2);
	}
	return p;
}

void * ckrealloc(void *ptr, size_t nbytes)
{
        void *p;

	if ((p = realloc(ptr, nbytes)) == NULL) {
	        perror("insmod: realloc failed");
		exit(2);
	}
	return p;
}

static int create_module(const char *name, unsigned long size)
{
	return syscall( __NR_create_module, name, size);
}

static int init_module(const char *name, void *code, unsigned codesize,
		struct mod_routines *routines,
		struct symbol_table *syms) {
	return syscall( __NR_init_module, name, code, codesize, routines,
		syms);
}

static int delete_module(const char *name)
{
	return syscall( __NR_delete_module, name);
}

static int get_kernel_syms(struct kernel_sym *buffer)
{
	return syscall( __NR_get_kernel_syms, buffer);
}

static void check_version(int force_load)
{
	unsigned long kernel_version;

	/* Check if module and kernel version match */
	kernel_version = looksym("kernel_version");
	if (strcmp( (char*) textseg + kernel_version, uts_info.release)) {
		insmod_error (
			"Error: The module was compiled on kernel version %s.\n"
			"       This kernel is version %s. They don't match!\n"
			"       Check that the module is usable with the current kernel,\n"
			"       recompile the module and try again.",
			(char*) textseg + kernel_version, uts_info.release);
		if (force_load)
			insmod_error("       Trying to load it anyway...");
		else
			exit( 2);
	}

}

/*
 * unversioned kernel, versioned module
 */
static int m_strncmp(const char *tabentry, const char *lookfor, size_t n)
{
	int len = strlen(lookfor);
	int retval;

	if ((retval = strncmp(tabentry, lookfor, len)) != 0)
		return retval;
	/* else */
	if ((strncmp(tabentry + len, "_R", 2) == 0) &&
		(strlen(tabentry + len) == 10))
		return 0;
	else
		return strcmp(tabentry, lookfor);
}

/*
 * versioned kernel, unversioned module
 */
static int k_strncmp(const char *tabentry, const char *lookfor, size_t n)
{
	int len = strlen(tabentry);
	int retval;

	if ((retval = strncmp(tabentry, lookfor, len)) != 0)
		return retval;
	/* else */
	if ((strncmp(lookfor + len, "_R", 2) == 0) &&
		(strlen(lookfor + len) == 10))
		return 0;
	else
		return strcmp(tabentry, lookfor);
}

struct strpatch {
	int where; /* offset from start */
	int what;
};
static struct strpatch *stringpatches;
static int n_stringpatches;

static void push_string(char *string, int patch_offset)
{
	int len;
	unsigned int string_offset = progsize;
	char *p;

	if ((p = strchr(string, ',')) != (char *)0)
		len = p - string;
	else
		len = strlen(string);

	/*
	 * If we want to use any string (even one with only numbers):
	 * enclose the string with quotes, i.e.  ' or "
	 * (most probably escaped, if from a shell)
	 *
	 * Inspired by Giorgio Caset <gca@wag.ch>
	 */
	if (((string[0] == '\'') || (string[0] == '"')) &&
	     (string[len - 1] == string[0])) {
		++string; /* strip quote */
		len -= 2; /* and quote at the end as well */
	}

	progsize += len + 1;
	/* JEJB: don't like this, but if we're copying strings, better make
	 * sure memory actually exists to copy them into */
	textseg = ckrealloc(textseg, progsize);
	strncpy(textseg + string_offset, string, len);
	*(textseg + string_offset + len) = '\0';

	if (n_stringpatches == 0)
		stringpatches = (struct strpatch *)ckalloc(sizeof(struct strpatch));
	else
		stringpatches = (struct strpatch *)ckrealloc(stringpatches,
			(n_stringpatches + 1) * sizeof(struct strpatch));

	(*(stringpatches + n_stringpatches)).where = patch_offset;
	(*(stringpatches + n_stringpatches)).what = string_offset;
	++n_stringpatches;
}

int main(int argc, char **argv)
{
	FILE *fp;
	struct exec *aouthdr = (struct exec *)&header;
	struct kernel_sym *curr_module = &nullsym;
	struct kernel_sym *ksymtab = NULL;
	struct kernel_sym *ksym = NULL;
	struct kernel_sym *resident_ksym_start = NULL;
	struct mod_routines routines;
	struct symbol *sp;
	struct strpatch *patch;
	unsigned long init_func, cleanup_func;
	int (*tabcomp) (const char *, const char *, size_t) = strncmp;
	int fatal_error;
	int i;
	int nksyms;
	int versioned_module;
	int found_resident = -1;
	int autoclean = 0; /* for insertions from kerneld: option "-k" */
	int bss_offset;

	struct symbol_table *newtab;
	int module_refs = 0; /* number of references modules */
	int n_symboldefs = 0; /* number of defined symbols in the module */
	int string_table_size = 0; /* size of the new symbol table string table */
	struct internal_symbol *symp;
	struct module_ref *refp;
	char *stringp;

	char *filename;
	char spare_path[200]; /* just testing... */
	char *modname = NULL;
	char *otextseg; /* JEJB: store the initial textseg, so we get offset */
	char *p;

	/* find basename */
	
	if ((p = strrchr(argv[0], '/')) != (char *)0)
		++p;
	else
		p = argv[0];

	if (strcmp(p, "rmmod") == 0)
		return (rmmod(argc, argv))?1:0;

	if (strcmp(p, "ksyms") == 0)
		return ksyms(argc, argv);

	/* else  this is insmod! */

	while (argc > 1 && (argv[1][0] == '-')) {
		p = &(argv[1][1]);
		while (*p) {
			switch (*p) {
			case 'f': /* force loading */
				force_load = 1;
				break;

			case 'k': /* module loaded by kerneld, auto-cleanable */
				autoclean = MOD_AUTOCLEAN;
				break;

			case 'm': /* generate loadmap */
				loadmap = 1;
				break;

			case 'o':
				modname = argv[2];
				--argc;
				++argv;
				break;

			case 'p': /* silent poll mode */
				silent_poll = 1;
				break;

			case 's':
				insmod_setsyslog("insmod");
				break;

			case 'v': /* verbose output */
				verbose = 1;
				break;

			case 'V':
				printf("Version " MODULES_VERSION "\n");
				break;

			case 'x': /* do _not_ export externs */
				export_flag = 0;
				break;

			case 'X': /* _do_ export externs */
				export_flag = 1;
				break;
			}
			++p;
		}
		--argc;
		++argv;
	}

	if (argc < 2) {
		fputs("Usage:\n"
		      "insmod [-fkmopsvVxX] [-o name] module [[sym=value]...]\n"
		      "\n"
		      "  module     Filename of a loadable kernel module (*.o)\n"
		      "  -f         Force loading under wrong kernel version\n"
		      "  -k         Make module (autoclean)-able\n"
		      "  -m         Generate loadmap (so crashes can be traced)\n"
		      "  -o name    Set internal modulname to name\n"
		      "  -p         Poll mode, just check if the module matches the kernel\n"
		      "  -s         Report errors via syslog\n"
		      "  -v         Verbose output\n"
		      "  -V         Show version\n"
		      "  -x         do *not* export externs\n"
		      "  -X         *do* export externs\n"
		      , stderr);
		exit(2);
	}

	uname(&uts_info);
	sprintf(kd_dbmfile, "/lib/modules/%s/persist.gdbm", uts_info.release);

	filename = argv[1];
	argv += 2;
	--argc;

	/* get the size of the current kernel symbol table */
	nksyms = get_kernel_syms(NULL);

	if (nksyms < 0) {
		insmod_error("get_kernel_sys failed: Cannot find Kernel symbols!");
		exit(2);
	}

	if (nksyms) {
		ksymtab = (struct kernel_sym *) ckalloc(nksyms * sizeof *ksymtab);
		/* NOTE!!! The order of the symbols is important */
		if (get_kernel_syms(ksymtab) != nksyms) {
			insmod_error ("Kernel symbol problem");
			exit(2);
		}
	}

	/* Is this a kernel with appended CRC-versions? */
	for (ksym = ksymtab, i = 0 ; i < nksyms ; ++i, ksym++) {
		if (found_resident == -1 && strcmp(ksym->name, "#") == 0) {
			resident_ksym_start = ksym;
			found_resident = i;
			break;
		}
	}

	/* look only in residents */
	if (found_resident != -1) {
		++ksym;
		/* The first symbol in a versioned kernel is _Using_Versions */
		if ((strcmp(ksym->name, "_Using_Versions") == 0) ||
		    (strcmp(ksym->name, "Using_Versions") == 0))
	    		versioned_kernel = 1;

		/* look at a "normal" symbol: */
		++ksym;
		if (((i + 1) < nksyms) && (ksym->name[0] != '_'))
			elf_kernel = 1;
	}

	/* construct the module name */
	if (modname == NULL) {
		int len;

		if ((p = strrchr(filename, '/')) != NULL)
			p++;
		else
			p = filename;
		len = strlen(p);
		if (len > 2 && strcmp(p + len - 2, ".o") == 0)
			len -= 2;
		else if (len > 4 && strcmp(p + len - 4, ".mod") == 0)
			len -= 4;

		modname = (char*) ckalloc(len + 1);
		memcpy(modname, p, len);
		modname[len] = '\0';
	}

	if ((strchr(filename, '/') == 0) && (strchr(filename, '.') == 0)) {
		struct stat dummy;
		char **defp;
		char *path;
		char *onepath;
		char *p;

		/* Look in the path given by MODPATH environment,
		 * if not found, look in the default directories.
                 * <storner@osiris.ping.dk> 950319 + Bjorn
		 */
		if ((path = getenv("MODPATH")) == 0)  {
			onepath = ckalloc(2000); /* or whatever */
			onepath[0] = '\0';
			for (defp = default_path; *defp; ++defp) {
				sprintf(spare_path, *defp, uts_info.release);
				strcat(onepath, spare_path);
				strcat(onepath, ":");
			}
		}
		else {
			onepath = ckalloc(strlen(path)+2);
			sprintf(onepath, "%s:", path);
		}

		for (path = onepath; (*path != '\0'); path += strlen(path)+1) {
			for (p = path; (*p != ':') && (*p != '\0'); p++)
				; 
			*p = '\0';
			sprintf(spare_path, "%s/%s", path, filename);
			if ((stat(spare_path, &dummy) >= 0) && (dummy.st_mode & S_IFREG))
				break;
			/* else */
			strcat(spare_path, ".o");
			if ((stat(spare_path, &dummy) >= 0) && (dummy.st_mode & S_IFREG))
				break;
		}
		free(onepath);
		filename = spare_path;
	}

	/* open file and read header */
	if ((fp = fopen(filename, "r")) == NULL) {
		insmod_error ("Cannot open %s", filename);
		exit(2);
	}

	/* sizeof(struct elfhdr) > sizeof(struct exec) */
	fread(&header, sizeof(Elf32_Ehdr), 1, fp);
	if (feof(fp) || ferror(fp)) {
		insmod_error ("Could not read header of %s", filename);
		exit(2);
	}

	symtab = (struct symbol *)0;
	nsymbols = 0;

	if (N_MAGIC((*aouthdr)) == OMAGIC) {
		char *errstr;

		if ((errstr = load_aout(fp)) != (char *)0) {
			insmod_error ("%s: %s", filename, errstr);
			exit(2);
		}
	}
	else if ((header.e_ident[0] == 0x7f) &&
		 (strncmp(&header.e_ident[1], "ELF",3) == 0) &&
		 (header.e_type == ET_REL) &&
#ifdef __i386__
		 ((header.e_machine == 3) || (header.e_machine == 6))
#endif
#ifdef __mc68000__
		 header.e_machine == EM_68K
#endif
#ifdef __sparc__
		 header.e_machine == EM_SPARC
#endif
		) {
			char *errstr;

			if ((errstr = load_elf(fp)) != (char *)0) {
				insmod_error ("%s: %s", filename, errstr);
				exit(2);
			}
	}
	else {
		insmod_error ("%s: not an object file", filename);
		exit(2);
	}

	/* JEJB: now we have textseg fixed after the load, so save value
	 * for later finding offset if realloc forces a move */
	otextseg = textseg;

	if (findsym("Using_Versions", NULL, strncmp))
		versioned_module = 1;
	else
		versioned_module = 0;

	/* check version info */
        if (verbose) {
		insmod_error ("versioned kernel: %s\nversioned module: %s",
			versioned_kernel ? "yes" : "no",
			versioned_module ? "yes" : "no");
		insmod_error ("%s kernel\n%s module",
			elf_kernel ? "ELF" : "a.out",
			aout_flag ? "a.out" : "ELF");
	}
	/*
	 * Logic:
	 *
	 * versioned_kernel versioned_module	action
	 * ================ ================	=============================
	 *	no		no		same kernel and module version
	 *					all symbols must match
	 *	no		yes		same kernel and module version
	 *					ignore symbol version suffix
	 *	yes		no		same kernel and module version
	 *					ignore symbol version suffix
	 *	yes		yes		all symbols must match,
	 *					including the version suffix
	 */
	switch ((versioned_kernel << 1) + versioned_module) {
	case 0: /* unversioned_kernel, unversioned_module */
		/* Uwe Bonnes <bon@elektron.ikp.physik.th-darmstadt.de>: */
		if (!force_load)
			check_version(force_load);
		tabcomp = strncmp;
		break;

	case 1: /* unversioned_kernel, versioned_module */
		check_version(force_load);
		tabcomp = m_strncmp;
		break;

	case 2: /* versioned_kernel,   unversioned_module */
		check_version(force_load);
		tabcomp = k_strncmp;
		break;

	case 3: /* versioned_kernel,   versioned_module */
		if (force_load)
			check_version(force_load);
		tabcomp = strncmp;
		break;

	}

	/* get initialization and cleanup routines */
	init_func = looksym("init_module");
	cleanup_func = looksym("cleanup_module");

	/* bind undefined symbols (ELF-stuff hidden in defsym()) */
	defsym(strncmp, "mod_use_count_", 0 - sizeof (int), N_BSS | N_EXT, TRANSIENT);

	/* First: resolve symbols using kernel transient symbols */
	/* Then: use resident kernel symbols to resolve the last ones... */
	for (ksym = ksymtab, i = 0 ; i < nksyms ; ++i, ksym++) {
		/* Magic in this version of the new get_kernel_syms:
		 * Every module is sent along as a symbol,
		 * where the module name is represented as "#module", and
		 * the address of the module struct is stuffed into the value.
		 * The name "#" means that the symbols that follow are
		 * kernel resident.
		 */
		if (ksym->name[0] == '#') {
			curr_module = ksym;
			continue;
		}

 		if (defsym(tabcomp, ksym->name +
		    ((!elf_kernel && (ksym->name[0] == '_'))?1:0),
			ksym->value, N_ABS | N_EXT,
		/* this is safe since curr_module was initialized properly */
			(curr_module->name[1]) ?  TRANSIENT : RESIDENT)) {
			/* kludge: mark referenced modules */
			if (curr_module->name[1] && /* but not the kernel */
				(curr_module->name[0] == '#')) {
				curr_module->name[0] = '+';
				++module_refs;
			}
		}
	}

	/* allocate space for "common" symbols */
	/* and check for undefined symbols */
	fatal_error = 0;
	bss_offset = codesize + bss1size;
	/*
	 * Scan symbol table to determine global bss size
	 */
	for (sp = symtab ; sp < symtab + nsymbols ; sp++) {
		if ((symname(sp) == (char *)0) || (*symname(sp) == '\0'))
			continue;

		if ( ((aout_flag && (sp->u.n.n_type == (N_UNDF | N_EXT))) ||
			(!aout_flag && is_global(sp) && is_undef(sp)))
			&& (symvalue(sp) != 0)) {
			int len;

			if (aout_flag) {
				sp->u.n.n_type = N_BSS | N_EXT;
				len = symvalue(sp);
 				if (verbose)
 				        insmod_error (
 					"bss2 sym %X, size =%7d: %s",
 			                bss_offset, len, symname( sp));
			}
			else { /* I'm not sure I understand this... */
				sp->u.e.st_info = (STB_GLOBAL << 4)| STT_OBJECT;
				sp->u.e.st_shndx = SHN_ABS;
				len = (sp->u.e.st_size)?(sp->u.e.st_size):4;
			}
 			symvalue(sp) = bss_offset;
 			bss_offset += len;
		} else if (is_undef(sp)) {
			if (versioned_module) {
				char *v = strrchr(symname(sp), '_');
				if (v && (strncmp(v, "_R", 2) == 0) &&
					(strlen(v) == 10))
					*v = '\0';
				insmod_error ("%s: wrong version or undefined",
					symname(sp));
				if (v && !(*v))
					*v = '_';
			}
			else
				insmod_error ("%s undefined", symname(sp));
			fatal_error = 1;
		}
	}
	if (fatal_error) {
		insmod_error (  "Loading failed! The module symbols "
				"(from linux-%s) don't match your linux-%s",
			(char*) textseg + looksym("kernel_version"),
			uts_info.release);
		exit(2);
	}

	/* Change values according to the parameters to "insmod" */
	{
		struct symbol *change;
		int *patchme;
		char *param;
		char *val;
		int value;

		while (argc > 1) {
			param = *argv;
			++argv;
			--argc;

			/*
			 * This makes the following contruct legal:
			 *    insmod module.o -o new_name
			 * This is used by the "options" configuration
			 * lines for modprobe.
			 */
			if (strcmp(param, "-o") == 0) {
				modname = (char*) ckalloc(strlen(argv[0]) + 1);
				strcpy(modname, argv[0]);
				--argc;
				++argv;
				continue;
			}
			if ((val = strchr(param, '=')) == NULL)
				continue;
			*val = '\0'; /* mark end of tag */

			if (versioned_module)
				change = findsym(param, NULL, m_strncmp);
			else
				change = findsym(param, NULL, strncmp);
			if (change == NULL) {
				insmod_error ("Symbol '%s' not found", param);
				exit(2);
			}
			patchme = (int *)(textseg + symvalue(change));

			do {
				++val;
#ifdef PERSIST
				/*
				 * This enables the useage of previously
				 * saved module persistent data to update
				 * the symbol value.
				 *
				 * Syntax:
				 *
				 *	symbol=?key:default,default...
				 *
				 * If no value can be found for the key,
				 * the default values will be used.
				 */
				if (*val == '?') {
#include <gdbm.h>
					GDBM_FILE dbf;
					datum key, data;
					char *x;

					key.dptr = val + 1;
					if ((x = strchr(val, ':')) != NULL) {
						*x = '\0';
						val = ++x;
					}
					if ((key.dptr[0] == '\'') ||
					    (key.dptr[0] == '"')) {
						if ((x = strchr(key.dptr, key.dptr[0]))) {
							*x = '\0';
					    		key.dptr++;
						}
					}
					key.dsize = strlen(key.dptr) + 1;

					if ((dbf = gdbm_open(kd_dbmfile, 0,
						    GDBM_READER, 0600, NULL))) {
						data = gdbm_fetch(dbf, key);
						gdbm_close(dbf);
						if (data.dptr) {
							/* found a value! */
							memcpy(patchme, data.dptr, data.dsize);
							free (data.dptr);
							/* ignore given info */
							break;
						}
					}
					/*
					 * No persistent data found,
					 * use the given info
					 */
					if (*val == '\0')
						break;
				}
#endif /* PERSIST */
				if (*val < '0' || '9' < *val) {
				      /*
				       * Textseg may change during calls to 
				       * push_string() so we store the offset
				       *
				       * Timo Kokkonen <timo@cs.ualberta.ca>
				       */
					if (*val != ',')
				     		push_string(val, symvalue(change));
				}
				else { /* numerical */
					if (val[0] == '0') {
						if (val[1] == 'x')
							sscanf(val, "%x", &value);
						else
							sscanf(val, "%o", &value);
					}
					else
						sscanf(val, "%d", &value);
					*patchme = value;
				}
				++patchme;
			} while ((val = strchr(val, ',')) != (char *)0);
		}
	}

	/*
	 * If we have survived this far, and are just checking,
	 * that's it...
	 */
	if (silent_poll)
		exit(0);

	/* create the module */
	errno = 0;
	/* make sure we have enough memory malloc'd for copy which kernel
	 * will perform */
	textseg = ckrealloc(textseg, progsize);
	/* We add "sizeof (int)" to skip over the use count */
	addr = create_module(modname, progsize) + sizeof (int);

	switch (errno) {
	case EEXIST:
		insmod_error ("A module named %s already exists", modname);
		exit(2);
	case ENOMEM:
		insmod_error ("Cannot allocate space for module");
		exit(2);
	case 0:
		break;
	default:
		perror("create_module");
		exit(2);
	}

	/* perform relocation */
	if (aout_flag)
		relocate_aout(fp, textseg - otextseg);
	else
		relocate_elf(fp, textseg - otextseg);

	/*
	 * Patch in any new strings from the command line
	 */
	for (patch = stringpatches; n_stringpatches-- > 0; ++patch) {
		/*
		 * Now we have to calculate the "absolute" address of the
		 * string and update the "pointer" in the module image...
		 *
		 * Timo Kokkonen <timo@cs.ualberta.ca>
		 *
		 * Pointer increment fixes by:
		 *   Alberto Vignani <alberto.vignani@torino.alpcom.it>
		 *   Wolfgang Wander <wwc@rem-wwc.desy.de>
		 */
		*((int *)(textseg + patch->where)) = addr + patch->what;
		/* insmod address */			 /* kernel address */
	}
	if (stringpatches)
		free(stringpatches);
	stringpatches = (struct strpatch *)0;
	n_stringpatches = 0;


	/*
	 * Handle module "administrativia"
	 */
	init_func += addr;
	cleanup_func += addr;

	/* "hide" the module specific symbols, not to be exported! */
	hidesym("cleanup_module");
	hidesym("init_module");
	hidesym("kernel_version");
	hidesym("mod_use_count_");

	/* Build the module symbol table */
	/* abuse of *_other:
	 * 0	refer kernel resident
	 * 1	define module symbol, inserted in kernel syms
	 * 2	refer module symbol
	 * 3	won't happen (define resolved by other module?)
	 */

	/*
	 * Get size info for the new symbol table
	 * we already have module_refs
	 */
	for (sp = symtab ; export_flag && (sp < symtab + nsymbols) ; sp++) {
		if (is_global(sp) && (symother(sp) & DEF_BY_MODULE)) {
			string_table_size += strlen(symname(sp)) + 1;
			if (!elf_kernel)
				++string_table_size;
			++n_symboldefs;
		}
	}
	newtab = (struct symbol_table *)ckalloc(sizeof(struct symbol_table) +
		n_symboldefs * sizeof(struct internal_symbol) +
		module_refs * sizeof(struct module_ref) +
		string_table_size);

	newtab->size = sizeof(struct symbol_table) +
		n_symboldefs * sizeof(struct internal_symbol) +
		module_refs * sizeof(struct module_ref) +
		string_table_size;

	newtab->n_symbols = n_symboldefs;
	newtab->n_refs = module_refs;

	symp = &(newtab->symbol[0]);
	stringp = ((char *)symp) + 
		n_symboldefs * sizeof(struct internal_symbol) +
		module_refs * sizeof(struct module_ref);

	/* update the string pointer (to a string index) in the symbol table */
	for (sp = symtab ; export_flag && (sp < symtab + nsymbols) ; sp++) {
		if (is_global(sp) && (symother(sp) & DEF_BY_MODULE)) {
			symp->addr = (void *)(symvalue(sp) + addr);
			symp->name = (char *)(stringp - (char *)newtab);
			if (!elf_kernel) {
				strcpy(stringp, "_");
				++stringp;
			}
			strcpy(stringp, symname(sp));
			stringp += strlen(symname(sp)) + 1;
			++symp;
		}
	}

	refp = (struct module_ref *)symp;
	for (ksym = ksymtab, i = 0 ; i < nksyms ; ++i, ksym++) {
		if (ksym->name[0] == '+') {
			refp->module = (struct module *)ksym->value;
			++refp;
		}
	}

	/* load the module into the kernel */
	/* NOTE: the symbol table == NULL if there are no defines/updates */
	routines.init = (int (*)(void)) init_func;
	routines.cleanup = (void (*)(void)) cleanup_func;

	if (init_module(modname, textseg,
		/* An autoclean marker for a non-kerneld-aware kernel
		 * will result in a rejection of the module.
		 * This is what we want, since the "-k" option is useful
		 * only with kerneld aware kernels...
		 */
		autoclean | progsize /*codesize*/, &routines,
		((n_symboldefs + module_refs)? newtab : NULL)) < 0) {

		if (errno == EBUSY) {
			insmod_error ("Initialization of %s failed", modname);
		} else {
			perror("init_module");
		}
		delete_module(modname);
		exit(1);
	}

	/*
	 * Print a loadmap so that kernel panics can be identified.
	 * Load map option conceived by Derek Atkins <warlord@MIT.EDU>
	 */
	for (sp = symtab; loadmap && (sp < symtab + nsymbols) ; sp++) {
		char symtype = 'u';

		if (aout_flag) {
			if ((sp->u.n.n_type & ~N_EXT) == N_ABS)
				continue;

			switch (sp->u.n.n_type & ~N_EXT) {
			case N_TEXT | N_EXT: symtype = 'T'; break;
			case N_TEXT: symtype = 't'; break;
			case N_DATA | N_EXT: symtype = 'D'; break;
			case N_DATA: symtype = 'd'; break;
			case N_BSS | N_EXT: symtype = 'B'; break;
			case N_BSS: symtype = 'b'; break;
			}
		}
#if 0
		else
			symtype = ' '; /* until someone does the work... */
#endif

		insmod_printf("%X %c %s\n",
			symvalue(sp) + addr, symtype, symname(sp));
	}

	if (nksyms > 0)
		free(ksymtab); /* it has done its job */

	exit(0);
}

void hidesym(const char *name)
{
	struct symbol *sp;

	if ((sp = findsym(name, NULL, strncmp)) != NULL) {
		if (aout_flag)
			sp->u.n.n_type &= ~N_EXT;
		else
			sp->u.e.st_info = (STB_LOCAL << 4) |
		    			  (ELF32_ST_TYPE(sp->u.e.st_info));
	}
}

int defsym(int (*pfi) (const char *, const char *, size_t),
	const char *name, unsigned long value, int type, int source)
{
	struct symbol *sp;
	/*
	 * field "*_other" abused in the symbol table
	 * in order to discriminate between defines and references
	 * and resolves by kernel resident or transient symbols...
	 */

	if ((sp = findsym(name, NULL, pfi)) == NULL)
		return 0; /* symbol not used */
	/* else */

	/*
	 * Multiply defined?
	 * Well, this is what we _want_!
	 * I.e. a module _should_ be able to replace a kernel resident
	 * symbol, or even a symbol defined by another module!
	 * NOTE: The symbols read from the kernel (the transient ones)
	 * MUST be handled in the order:
	 *   last defined, ..., earlier defined, ...
	 */
	if (aout_flag) {
		if (sp->u.n.n_type != (N_UNDF | N_EXT))
			return 0; /* symbol not used */
		sp->u.n.n_type = type;
	}
	else { /* elf */
		if ((ELF32_ST_BIND(sp->u.e.st_info) == STB_LOCAL) ||
		    (sp->u.e.st_shndx != SHN_UNDEF))
			return 0; /* symbol not used */
		/* hack follows... */
		if (type & N_ABS)
			sp->u.e.st_info = (STB_GLOBAL << 4);
		else
			sp->u.e.st_info = (STB_LOCAL << 4);
		sp->u.e.st_shndx = SHN_ABS; /* or whatever */
	}

	symother(sp) |= source;
	symvalue(sp) = value;

	return 1; /* symbol used */
}


/*
 * Look up an name in the symbol table.  If "add" is not null, add a
 * the entry to the table.  The table is stored as a splay tree.
 */
struct symbol * findsym(const char *key, struct symbol *add,
	int (*pfi) (const char *, const char *, size_t))
{
	struct symbol *left, *right;
	struct symbol **leftp, **rightp;
	struct symbol *sp1, *sp2, *sp3;
	int cmp;
	int path1, path2;

	if (add) {
		if (aout_flag)
			add->u.n.n_un.n_name = (char *)key;
		else
			add->u.e.st_name = (Elf32_Word)key;
	}
	sp1 = symroot;
	if (sp1 == NULL)
		return add? symroot = add : NULL;
	leftp = &left, rightp = &right;
	for (;;) {
		cmp = (*pfi)(symname(sp1), key, SYM_MAX_NAME);
		if (cmp == 0)
			break;
		if (cmp > 0) {
			sp2 = sp1->child[0];
			path1 = 0;
		} else {
			sp2 = sp1->child[1];
			path1 = 1;
		}
		if (sp2 == NULL) {
			if (! add)
				break;
			sp2 = add;
		}
		cmp = (*pfi)(symname(sp2), key, SYM_MAX_NAME);
		if (cmp == 0) {
one_level_only:
			if (path1 == 0) {	/* sp2 is left child of sp1 */
				*rightp = sp1;
				rightp = &sp1->child[0];
			} else {
				*leftp = sp1;
				leftp = &sp1->child[1];
			}
			sp1 = sp2;
			break;
		}
		if (cmp > 0) {
			sp3 = sp2->child[0];
			path2 = 0;
		} else {
			sp3 = sp2->child[1];
			path2 = 1;
		}
		if (sp3 == NULL) {
			if (! add)
				goto one_level_only;
			sp3 = add;
		}
		if (path1 == 0) {
			if (path2 == 0) {
				sp1->child[0] = sp2->child[1];
				sp2->child[1] = sp1;
				*rightp = sp2;
				rightp = &sp2->child[0];
			} else {
				*rightp = sp1;
				rightp = &sp1->child[0];
				*leftp = sp2;
				leftp = &sp2->child[1];
			}
		} else {
			if (path2 == 0) {
				*leftp = sp1;
				leftp = &sp1->child[1];
				*rightp = sp2;
				rightp = &sp2->child[0];
			} else {
				sp1->child[1] = sp2->child[0];
				sp2->child[0] = sp1;
				*leftp = sp2;
				leftp = &sp2->child[1];
			}
		}
		sp1 = sp3;
	}
	/*
	 * Now sp1 points to the result of the search.  If cmp is zero,
	 * we had a match; otherwise not.
	 */
	*leftp = sp1->child[0];
	*rightp = sp1->child[1];
	sp1->child[0] = left;
	sp1->child[1] = right;
	symroot = sp1;
	return cmp == 0? sp1 : NULL;
}


unsigned long looksym(const char *name)
{
	struct symbol *sp;

	sp = findsym(name, NULL, strncmp);
	if (sp == NULL) {
		insmod_error ("%s needed, but can't be found", name);
		exit(2);
	}
	return symvalue(sp);
}

/*
 * This is (was) rmmod, now merged with insmod!
 *
 * Original author: Jon Tombs <jon@gtex02.us.es>,
 * and extended by Bjorn Ekwall <bj0rn@blox.se> in 1994 (C).
 * See the file COPYING for your rights (GNU GPL)
 */

#define WANT_TO_REMOVE 1
#define CAN_REMOVE 2

struct ref {
	int modnum;
	struct ref *next;
};

struct mod {
	int status; /* or of: WANT_TO_REMOVE, CAN_REMOVE */
	char name[MOD_MAX_NAME];
	struct ref *ref;
};

static struct mod *loaded;
static int current = -1;

/* build the references as shown in /proc/ksyms */
static void get_stacks()
{
	FILE *fp;
	struct ref *rp;
	int i;
	char line[200]; /* or whatever... */
	char *p;
	char *r;

	if ((fp = fopen("/proc/modules", "r")) == (FILE *)0) {
		perror("/proc/modules");
		exit(1);
	}

	while (fgets(line, 200, fp)) {
		if (loaded) {
			++current;
			loaded = (struct mod *)ckrealloc(loaded,
				(current + 1) * sizeof(struct mod));
		} else {
			current = 0;
			loaded = (struct mod *)ckalloc(sizeof(struct mod));
		}

		loaded[current].status = 0;
		loaded[current].ref = (struct ref *)0;

		/* save the module name */
		p = strchr(line, ' ');
		*p = '\0';
		strcpy(loaded[current].name, line);

		/* any references? */
		if ((p = strchr(p + 1, '['))) {
			*(strrchr(p + 1, ']')) = '\0';
			do {
				r = p + 1;
				if ((p = strchr(r, ' ')))
					*p = '\0';
				for (i = 0; i < current; ++i) {
					if (strcmp(loaded[i].name, r) == 0)
						break;
				}

				if (i == current) { /* not found! */
					insmod_error (
					"Strange reference in "
					"/proc/modules: '%s' used by '%s'?",
					loaded[current].name, r);
					exit(1);
				}

				rp = (struct ref *)ckalloc(sizeof(struct ref));
				rp->modnum = i;
				rp->next = loaded[current].ref;
				loaded[current].ref = rp;
			} while (p);
		}
	}

	fclose(fp);
}

int rmmod(int argc, char **argv)
{
	struct ref *rp;
	int i, j;
	int errors = 0;
	int count;
	char **list;
	char *p;

	if (argc == 1) {
		fprintf(stderr, "usage: rmmod [-r] [-s] module ...\n");
		return 1;
	}
	/* else */

	if (strcmp(argv[1], "-r") != 0) {
		if (strcmp(argv[1], "-a") == 0) {
			/* delete all unused modules and stacks */
			return (delete_module(NULL))?1:0;
		}
		/* else */
		if (strcmp(argv[1], "-s") == 0){
			insmod_setsyslog("rmmod");
			--argc;
			++argv;
		}
		while (argc > 1) {
			if ((p = strrchr(argv[1], '.')) &&
				((strcmp(p, ".o") == 0) ||
				 (strcmp(p, ".mod") == 0))) 
					*p = '\0';
			if (delete_module(argv[1]) < 0) {
				++errors;
				perror(argv[1]);
			}
			--argc;
			++argv;
		}
		return errors;
	}
	/* else recursive removal */

	count = argc - 2;
	list = &(argv[2]);
	if (count <= 0) {
		insmod_error ("usage: rmmod [-r] module ...");
		return 1;
	}

	get_stacks();

	for (i = 0; i < count; ++i) {
		for (j = 0; j <= current; ++j) {
			if (strcmp(loaded[j].name, list[i]) == 0) {
				loaded[j].status = WANT_TO_REMOVE;
				break;
			}
		}
		if (j > current) {
			insmod_error("module '%s' not loaded", list[i]);
			++errors;
		}
	}

	for (i = 0; i <= current; ++i) {
		if (loaded[i].ref || (loaded[i].status == WANT_TO_REMOVE))
			loaded[i].status |= CAN_REMOVE;

		for (rp = loaded[i].ref; rp; rp = rp->next) {
			switch (loaded[rp->modnum].status) {
			case CAN_REMOVE:
			case WANT_TO_REMOVE | CAN_REMOVE:
				break;

			case WANT_TO_REMOVE:
				if (loaded[rp->modnum].ref == (struct ref *)0)
					break;
				/* else fallthtough */
			default:
				loaded[i].status &= ~CAN_REMOVE;
				break;
			}
		}

		switch (loaded[i].status) {
		case CAN_REMOVE:
		case WANT_TO_REMOVE | CAN_REMOVE:
			if (delete_module(loaded[i].name) < 0) {
				++errors;
				perror(loaded[i].name);
			}
			break;

		case WANT_TO_REMOVE:
			insmod_error("module '%s' is in use!", loaded[i].name);
			++errors;
			break;
		}
	}

	return errors;
}


/*
 * Used to be ksyms.c, but merged as well...
 *
 * Get kernel symbol table(s).
 *
 * Bjorn Ekwall <bj0rn@blox.se> in 1994 (C)
 * See the file COPYING for your rights (GNU GPL)
 */

int ksyms(int argc, char **argv)
{
	struct kernel_sym *ksymtab = NULL;
	struct kernel_sym *ksym = NULL;
	struct module module_struct;
	int nksyms;
	int i;
	int kmem = 0;
	int allsyms = 0;
	int show_header = 1;
	char *p;
	char *module_name = "";

	while (argc > 1 && (argv[1][0] == '-')) {
		p = &(argv[1][1]);
		while (*p) {
			switch (*p) {
			case 'a':
				allsyms = 1;
				break;

			case 'm':
				if ((kmem = open("/dev/kmem", O_RDONLY)) < 0) {
					perror("/dev/kmem");
					exit(2);
				}
				break;

			case 'h':
				show_header = 0;
				break;
			}
			++p;
		}
		--argc;
		++argv;
	}

	if (argc < 1) {
		fputs("Usage: ksyms [-a] [-h]\n", stderr);
		return 2;
	}

	/* get the size of the current kernel symbol table */
	nksyms = get_kernel_syms(NULL);

	if (nksyms < 0) {
		insmod_error ("get_kernel_sys failed: Cannot find Kernel symbols!");
		return 2;
	}

	if (nksyms) {
		ksymtab = (struct kernel_sym *) ckalloc(nksyms * sizeof *ksymtab);
		/* NOTE!!! The order of the symbols is important */
		if (get_kernel_syms(ksymtab) != nksyms) {
			insmod_error("Kernel symbol problem");
			return 2;
		}
	}

	if (show_header)
		printf("Address  Symbol    \tDefined by\n");

	for (ksym = ksymtab, i = 0 ; i < nksyms ; ++i, ksym++) {
		/* Magic in this version of the new get_kernel_syms:
		 * Every module is sent along as a symbol,
		 * where the module name is represented as "#module", and
		 * the address of the module struct is stuffed into the value.
		 * The name "#" means that the symbols that follow are
		 * kernel resident.
		 */
		if (ksym->name[0] == '#') {
			module_name = ksym->name + 1;
			if (!allsyms && (*module_name == '\0'))
				break;
			/* else */
			if (kmem) {
				if (
		(lseek(kmem, (off_t)ksym->value, SEEK_SET) > 0) &&
		(read(kmem, (char *)&module_struct, sizeof(struct module)) ==
		sizeof(struct module))) {
					printf("%lX --- (%dk) ---\t[%s]\n",
					(long)module_struct.addr,
					module_struct.size * 4,
					module_name);
				}
				else {
					perror("/dev/kmem");
					return 2;
				}
			}
			continue;
		}

		printf("%lX %s", ksym->value, ksym->name);
		if (*module_name)
			printf("\t[%s]", module_name);
		printf("\n");
	}

	return 0;
}
