#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/param.h>
#include "link.h"
#include "kernelsyms.h"

static char verbose_flag = 0;
static int from_kerneld = 0;


static void verbose (const char *ctl, ...)
{
	if (verbose_flag){
		va_list list;
		va_start (list,ctl);
		vprintf (ctl,list);
		va_end (list);
		fflush (stdout);
	}
}

/*
	Strip the extension of a file name
	Return a pointer to a static buffer
*/
const char *stripo(const char *fname)
{
	const char *pt = fname;
	while ((pt=strchr(fname,'/'))!=NULL) fname = pt+1;
	if ((pt = strrchr(fname, '.'))
		&& ((strcmp(pt, ".o") == 0) || (strcmp(pt, ".mod") == 0))) {
		char *leak = (char *)malloc(pt - fname + 1);
		strncpy(leak, fname, pt - fname);
		leak[pt - fname] = '\0';
		fname = leak;
	}
	return fname;
}

/*
	Return the options associated with a module.
	Return NULL if there is none.
*/
static char *any_options(const char *mod)
{
	const char *modname = stripo(mod);
	int len = strlen(modname);
	int i;

	for (i = 0; optlist[i]; ++i) {
		if (strncmp(optlist[i], modname, len) == 0
			&& isspace (optlist[i][len])) {
			return str_skip(&(optlist[i][len]));
		}
	}
	return NULL;
}

class NODE{
public:
	NODE *next;
	NODE *info;
	char *str;
	/*~PROTOBEG~ NODE */
public:
	NODE (const char *_str, NODE *_next);
	NODE *lookup (const char *name);
	~NODE (void);
	/*~PROTOEND~ NODE */
};

PUBLIC NODE::NODE(const char *_str, NODE *_next)
{
	next = _next;
	info = NULL;
	str = strdup_err (_str);
}

PUBLIC NODE::~NODE()
{
	delete info;
	delete next;
	free (str);
}

PUBLIC NODE * NODE::lookup (const char *name)
{
	NODE *ret = NULL;
	if (strcmp(name,str)==0){
		ret = this;
	}else if (next != NULL){
		ret = next->lookup(name);
	}
	return ret;
}

class DEPEND{
	NODE *in_kernel;
	NODE *dep_file;
	/*~PROTOBEG~ DEPEND */
public:
	DEPEND (void);
	int insmod (const char *mod,
		 NODE **newin_kernel,
		 char *options[]);
	int read (const char *cfgfile);
	int readcur (void);
	int unload (const char *mod);
	~DEPEND (void);
	/*~PROTOEND~ DEPEND */
};

PUBLIC DEPEND::DEPEND()
{
	in_kernel = NULL;
	dep_file = NULL;
}
PUBLIC DEPEND::~DEPEND()
{
	delete in_kernel;
	delete dep_file;
}
/*
	Read the liste of module already loaded in the kernel
*/
PUBLIC int DEPEND::readcur ()
{
	int ret = 0;
	delete in_kernel;
	in_kernel = NULL;
	struct kernel_sym *ksym;
	int so_far = 0;

	load_kernel_symbols();

	for (ksym = ksymtab; so_far < nksyms ; ++so_far, ksym++) {
		if (ksym->name[0] == '#') {
			if (ksym->name[1]) {
				in_kernel = new NODE(ksym->name + 1,in_kernel);
			}
			else
				break;
		}
	}
	return ret;
}
/*
	Read the dependancy file.
	The format is like a makefile.
*/
PUBLIC int DEPEND::read (const char *cfgfile)
{
	int ret = -1;
	int line_no;
	FILE *fin = fopen (cfgfile,"r");
	if (fin == NULL) {
		depmod_error ("Can't open dependancies file %s (%s)"
			,cfgfile,strerror(errno));
		return ret;
	}
	char buf[3000];
	ret = 0;

	while(fgets_strip(buf,sizeof(buf)-1,fin, &line_no)!=NULL){
		char line[3000];
		str_strip (buf,line);
		if (line[0] == '\0')
			continue;

		char *pt = line;
		while (isspace(*pt)) pt++;

		// Extract the lhs
		char *modname = pt;
		while (*pt != ':' && *pt != '\0' && !isspace(*pt)) pt++;
		if (*pt != ':') {
			depmod_error ("line %d: Invalid dependancy\n\t%s", line_no, line);
			ret = -1;
			break;
		}
		*pt++ = '\0';
		dep_file = new NODE (modname,dep_file);

		// Parse the list of modules
		while (*pt != '\0'){
			while (isspace (*pt)) pt++;
			if (*pt > ' ') {
				char *depname = pt;
				while (*pt > ' ') pt++;
				if (*pt != '\0') *pt++ = '\0';
				dep_file->info = new NODE (depname
					,dep_file->info);
			}
		}
	}
	fclose (fin);
	return ret;
}

static int call_rmmod(const char *mod)
{
	int ret = 0;
	char *ex;
	mod = stripo(mod);

#if 1
	if ((ex = exec_cmd(EXEC_PRE_REMOVE, mod)) != NULL) {
		if (system(ex) != 0)
			depmod_error("pre-remove %s failed\n", mod);
	}

	if ((ex = exec_cmd(EXEC_REMOVE, mod)) != NULL) {
		if ((ret = system(ex)) != 0)
			depmod_error("remove %s failed\n", mod);
	}
	else
		if ((ret = delete_module(stripo(mod))) < 0 )
			perror(stripo(mod));

	if (!ret && (ex = exec_cmd(EXEC_POST_REMOVE, mod)) != NULL) {
		if (system(ex) != 0)
			depmod_error("post-remove %s failed\n", mod);
	}
#else
	char cmd[300];
	sprintf (cmd,"/sbin/rmmod %s %s",(depmod_syslog ? "-s" : ""),mod);
	// This special sequence is there so error message generated
	// by rmmod are indented.
	verbose ("\r\t%s\n\t\t",cmd);
	int ret = system(cmd);
#endif
	return ret;
}
/*
	Unload all submodule in reverse order they were loaded.
	Return -1 if any error.
*/
static int rmmod (NODE *nod)
{
	int ret = 0;
	if (nod != NULL){
		ret = rmmod (nod->next);
		if (ret == 0){
			ret = call_rmmod(nod->str);
		}
	}
	return ret;
}

static NODE *lookup (NODE *nod, const char *str)
{
	NODE *ret = NULL;
	if (nod != NULL){
		ret = nod->lookup (str);
	}
	return ret;
}

/*
	Try to load a module and the sub-module needed.
	Return -1 if any error.

	If the module can't ne loaded, undo everything.
*/
PUBLIC int DEPEND::insmod (
	const char *mod,
	NODE **newin_kernel,	// modules added by this session
	char *options[])
{
	int ret = 0;
	if (mod != NULL
		&& lookup (in_kernel,stripo(mod))==NULL
		&& lookup (*newin_kernel,mod)==NULL){
		NODE *nod = lookup (dep_file,mod);
		if (nod == NULL){
			depmod_error ("No dependancy information for module %s",mod);
			return -1;
		}
		/* else */
		NODE *dep = nod->info;
		while (dep != NULL && ret == 0){
			ret = insmod (dep->str,newin_kernel,NULL);
			dep = dep->next;
		}

		if (ret == 0) {
			char cmd[300];
			sprintf (cmd,"/sbin/insmod %s %s %s %s"
				,from_kerneld ? "-k" : ""
				,depmod_syslog ? "-s" : ""
				,insmod_opt ? insmod_opt : ""
				,mod);
			char *op;
			if (options && options[1] &&
				strchr(options[1], '=')) {
				int nopt;
				for (nopt = 1; options[nopt]; ++nopt) {
					if (strchr(options[nopt], '=')){
						strcat(cmd, " ");
						strcat(cmd, options[nopt]);
					}
					else
						break;
				}
			}
			else {
				if ((op = any_options(mod))) {
					strcat(cmd, " ");
					strcat(cmd, op);
				}
				if (options && options[0] &&
				    (op = any_options(options[0]))) {
					strcat(cmd, " ");
					strcat(cmd, op);
				}
			}
			verbose ("\r\t%s\n\t\t",cmd);
			/* For now, seems like insmod is not reporting */
			/* success or failure correctly, so we read */
			/* /proc/modules each time */
			char *ex;
			if ((ex = exec_cmd(EXEC_PRE_INSTALL, mod)) != NULL) {
				if ((ret = system(ex)) != 0)
					depmod_error("pre-install %s failed\n", mod);
			}

			if (!ret) {
				if ((ex = exec_cmd(EXEC_INSTALL, mod)) != NULL)
					ret = system(ex);
				else
					ret = system(cmd);
			}

			if (!ret && (ex = exec_cmd(EXEC_POST_INSTALL, mod)) != NULL) {
				if ((ret = system(ex)) != 0)
					depmod_error("post-install %s failed\n", mod);
			}

			if (ret != 0){
				// Must unload all the sub-module
				rmmod (nod->info);
			}else{
				*newin_kernel = new NODE (mod,*newin_kernel);
			}
		}
		else {
			rmmod (nod->info);
			readcur();
		}
	}
	return (ret)?-1:0;
}
/*
	Unload a module and whatever modules was requiered by this module.
	We blindly remove everything in order, even if a module is needed
	by another module. The idea is that this module will refuse to
	unload.

	Return -1 if the first module cound not be unloaded.
*/
PUBLIC int DEPEND::unload (
	const char *mod)
{
	int ret = 0;
	if (mod != NULL){
		char *tbpath[1000];
		int nbl = config_locate (mod,tbpath,NULL);
		if (nbl == 0){
			/* #Specification: modprobe -r / unknown module
				If there is no information about a module in
				the dependancy file, we simply call /sbin/rmmod
				on the module without further checking.
			*/
			ret = call_rmmod(mod);
		}else{
			int m;
			for (m=0; m<nbl; m++){
				char *path = tbpath[m];
				if(lookup (in_kernel,stripo(path))!=NULL){
					NODE *nod = lookup (dep_file,path);
					if (nod == NULL){
						depmod_error (
							"No dependancy information for module %s"
							,mod);
					}else{
						ret = call_rmmod (path);
						/* We don't care if we succeed */
						/* to unload sub-modules. */
						NODE *dep = nod->info;
						while (dep != NULL){
							unload (dep->str);
							dep = dep->next;
						}
					}
				}
			}
			tbstr_free (tbpath,nbl);
		}
	}
	return ret;
}



static int modprobe_fromlist (
	DEPEND &dep,
	char *list[],
	int nb,
	const char *type,
	int loadall)
{
	int ret = -1;
	int i;
	for (i=0; i<nb; i++){
		NODE *newin_kernel = NULL;
		char *tbpath[1000];
		/* #Specification: modprobe / module option
			We can pass option to module in the modprobe's command line.
			It goes like this:
			#
			/sbin/modprobe module opt1=value opt2=value [ othermodule ...]
			#
			An option is a keyword followed by an equal sign and a value.
			No space are allowed in the sequence, unless it is quoted.

			The option list end at the end of the list or at the
			first non-option argument (a module).
		*/
		if (strchr(list[i], '=') == NULL) {
			int nbl = config_locate (list[i],tbpath,type);
			if (nbl == 0){
				depmod_error ("Can't locate module %s",list[i]);
			}else{
				int m;
				for (m=0; m<nbl; m++){
					if (dep.insmod (tbpath[m],&newin_kernel,&list[i]) == 0) {
						ret = 0;
						if (!loadall) break;
					}
				}
				tbstr_free (tbpath,nbl);
			}
		}
		delete newin_kernel;
		if (ret == 0 && !loadall) break;
	}
	return ret;
}
/*
	Print all available module matching "pattern" and of a certain type.
	type may be NULL.
*/
static void modprobe_printlist (
	const char *pattern,
	const char *type)
{
	char *lst[1000];
	int nb = config_lstmod (pattern,type,lst,1);
	int i;
	for (i=0; i<nb; i++) printf ("%s\n",lst[i]);
	tbstr_free (lst,nb);
}

static void modprobe_nothing(const char *str)
{
	depmod_error (
		"Nothing to %s ???\n"
		"Specify at least a module or a wildcard like \\*",str);
}

int modprobe_main (int argc, char *argv[])
{
	DEPEND dep;
	int ret = 0;
	int loadall = 0;	// Load only one module out of a list
	char *type = NULL;	// Search in all path[]
	int remove = 0;
	int showconfig = 0;
	int list = 0;

	if (argc == 1){
		fprintf (stderr,
			"modprobe " DEPMOD_RELEASE "\n"
			"Load/Unload modules with dependancies\n"
			"\n"
			"modprobe [-a] [ -t type ] module1 module2 ...\n"
			"modprobe -c\n"
			"\n"
			"  modprobe will try to load one of module1 module2 and will\n"
			"  automaticly load the modules needed by module1 or module2\n"
			"  using the dependancy file (see " ETC_CONF_MODULES ").\n"
			"  If type is specified, the modules will be search only\n"
			"  in the corresponding directory (See path[type]=... in\n"
			"  " ETC_CONF_MODULES "). Note that modprobe can expand\n"
			"  wildcards, so\n"
			"\n"
			"      modprobe -t net \\* \n"
			"  will load one of the ethernet driver.\n"
			"\n"
			"  Unless -a option is provided, modprobe will\n"
			"  stop loading as soon as one module load sucessfully\n"
			"\n"
			"      modprobe -a -t boot \\*\n"
			"  will load all module of type boot.\n"
			"\n"
			"modprobe -r module\n"
			"  remove a module and all sub-modules it depends on\n"
			"\n"
			"modprobe -l [ -t type ] [ pattern ]\n"
			"  will list all modules available. With option -t, it will\n"
			"  shows only those of a certain type. A pattern may be used\n"
			"  to limit the output.\n"
			"\n"
			"modprobe -c\n"
			"  will display the current configuration\n"
			"\n"
			"It is calling /sbin/insmod and /sbin/rmmod to achieve its goal.\n"
			);

			return -1;
	}
	/* else */
	if (config_read(NULL) == -1)
		return -1;
	/* else */

	if ((dep.read(config_getdepfile()) == -1) || (dep.readcur() == -1))
		return -1;
	/* else */

	for (++argv, --argc; argc > 0 && argv[0][0] == '-'; ++argv, --argc) {
		char *arg = argv[0];
		while (*++arg) {
			switch (*arg) {
			case 't':
				if (argc > 1) {
					type = argv[1];
					++argv;
					--argc;
				}
				else {
					depmod_error ("Missing value for option -t");
					ret = -1;
				}
				break;

			case 'a': loadall = 1;
				break;

			case 'c': showconfig = 1;
				break;

			case 'd': debugmode = 1;
				break;

			case 'l': list = 1;
				break;

			case 'r': remove = 1;
				break;

			case 'v': verbose_flag = 1;
				break;

			case 'k': from_kerneld = 1;
				break;

			case 's': depmod_setsyslog ("modprobe");
				break;

			case 'V': printf("modprobe version %s\n",
					DEPMOD_RELEASE);
				break;

			default: depmod_error ("Invalid option %c", *arg);
				ret = -1;
				break;
			}
		}
	}

	if (ret == -1)
		return -1;

	/*
	 * argv now points to the first non-option argument
	 * argc is the remaining argument count
	 */

	if (showconfig)
		config_show();
	else if (remove) {
		if (argc > 0)
			for (; argc > 0 && ret == 0; ++argv, --argc)
				ret = dep.unload (*argv);
		else
			modprobe_nothing ("remove");
	}
	else if (list) {
		if (argc > 0)
			for (; argc > 0 && ret == 0; ++argv, --argc)
				modprobe_printlist (*argv, type);
		else
			modprobe_printlist ("*", type);
	}
	else {
		if (argc > 0)
			ret = modprobe_fromlist (dep,argv,argc,type,loadall);
		else
			modprobe_nothing ("load");
	}

	verbose ("\n");
	return ret;
}
