/*
 * Handle the configuration, including /etc/modules.conf
 *
 * Copyright 1994, 1995, 1996, 1997:
 *	Jacques Gelinas <jack@solucorp.qc.ca>
 *	Björn Ekwall <bj0rn@blox.se> February 1999
 *	Keith Owens <kaos@ocs.com.au> October 1999
 *
 * "kernelversion" idea from the Debian release via:
 *      Wichert Akkerman <wakkerma@cs.leidenuniv.nl>
 *
 * Björn, inspired by Richard Henderson <rth@twiddle.net>, cleaned up
 * the wildcard handling and started using ftw in March 1999
 * Cleanup of hardcoded arrays: Björn Ekwall <bj0rn@blox.se> March 1999
 * Many additional keywords: Björn Ekwall <bj0rn@blox.se> (C) March 1999
 * Standardize on /etc/modules.conf Keith Owens <kaos@ocs.com.au> October 1999
 *
 * Alpha typecast:Michal Jaegermann <michal@ellpspace.math.ualberta.ca>
 *
 * This file is part of the Linux modutils.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Specification: /etc/modules.conf / format
 *	Modules may be located at different places in the filesystem.
 *
 *	The file /etc/modules.conf contains different definitions to
 *	control the manipulation of modules.
 *
 *	Standard Unix style comments and continuation line are supported.
 *	Comments begin with a # and continue until the end of the line.
 *	A line continues on the next one if the last non-white character
 *	is a \.
 */
/* #Specification: /etc/modules.conf / format / official name */

#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <signal.h>
#include <limits.h>
#include <sys/param.h>
#include <errno.h>

#include "util.h"
#include "config.h"
#include "alias.h"

int flag_autoclean; /* set/used by modprobe and insmod */

struct utsname uts_info;

struct PATH_TYPE *modpath;
int nmodpath = 0;
static int maxpath = 0;

struct EXEC_TYPE *execs;
int nexecs = 0;
static int maxexecs = 0;

OPT_LIST *opt_list;
static int n_opt_list;

OPT_LIST *abovelist;
static int n_abovelist;

OPT_LIST *belowlist;
static int n_belowlist;

OPT_LIST *prunelist;
static int n_prunelist;

OPT_LIST *probe_list;
static int n_probe_list;

OPT_LIST *probeall_list;
static int n_probeall_list;

OPT_LIST *aliases;
static int n_aliases;

char *persistdir = "/var/lib/modules/persist";

const char symprefix[] = SYMPREFIX;

char *insmod_opt = NULL;
char *config_file = NULL;	/* Which file was actually used */
time_t config_mtime;
int root_check_off = CONFIG_ROOT_CHECK_OFF;	/* Default is modules must be owned by root */
static char *config_version;	/* Hack for config_add */
int quick = 0;			/* Option -A */

/* The initialization order must match the gen_file_enum order in config.h */
struct gen_files gen_file[] = {
	{"generic_string", NULL, 0},
	{"pcimap", NULL, 0},
	{"isapnpmap", NULL, 0},
	{"usbmap", NULL, 0},
	{"parportmap", NULL, 0},
	{"ieee1394map", NULL, 0},
	{"pnpbiosmap", NULL, 0},
	{"dep", NULL, 0},
};

const int gen_file_count = sizeof(gen_file)/sizeof(gen_file[0]);

int flag_verbose;

unsigned long safemode;

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


/*
 *	Check to see if the existing modules.xxx files need updating,
 *	based on the timestamps of the modules and the config file.
 */
static int check_update (const char *file, const struct stat *sb)
{
	int len = strlen(file);
	int i;

	if (!S_ISREG(sb->st_mode))
		return 0;
	for (i = 0; i < gen_file_count; ++i) {
		if (sb->st_mtime > gen_file[i].mtime)
			break;
	}
	if (i == gen_file_count)
		return 0;	/* All generated files are up to date */

	if (len > 2 && !strcmp(file + len - 2, ".o"))
		return 1;
	else if (len > 4 && !strcmp(file + len - 4, ".mod"))
		return 1;
#ifdef CONFIG_USE_ZLIB
	else if (len > 5 && !strcmp(file + len - 5, ".o.gz"))
		return 1;
#endif
	return 0;
}

static int need_update (const char *force_ver, const char *base_dir)
{
	struct stat tmp;
	char dep[PATH_MAX];
	int i;
	uname (&uts_info);
	if (!force_ver)
		force_ver = uts_info.release;

	if (strlen (force_ver) > 50)
		/* That's just silly. */
		return 1;

	for (i = 0; i < gen_file_count; ++i) {
		if (stat(gen_file[i].name, &tmp))
			return 1;	/* No dependency file yet, so we need to build it. */
		gen_file[i].mtime = tmp.st_mtime;
	}

	if (stat ("/etc/modules.conf", &tmp) &&
	    stat ("/etc/conf.modules", &tmp))
		return 1;

	for (i = 0; i < gen_file_count; ++i) {
		if (tmp.st_mtime > gen_file[i].mtime)
			return 1;	/* Config file is newer. */
	}

	snprintf (dep, sizeof(dep), "%s/lib/modules/%s", base_dir, force_ver);
	return xftw (dep, check_update);
}


/*
 *	Strip white char at the end of a string.
 *	Return the address of the last non white char + 1 (point on the '\0').
 */
static char *strip_end(char *str)
{
	int len = strlen(str);

	for (str += len - 1; len > 0 && (isspace(*str)); --len, --str)
		*str = '\0';
	return str + 1;
}

/*
 *	Read a line using fgets, expanding the buffer as necessary until \n is
 *	seen.  If *p_buf is NULL the sizebuf is ignored, otherwise sizebuf must
 *	contain the malloc() size of *p_buf.
 */
static char *fgets_local(char **p_buf, int *sizebuf, FILE * fin)
{
	char *p;
	char *ret = NULL;
	if (!*p_buf)
		*p_buf = xmalloc(*sizebuf = 3000);
	**p_buf = '\0';
	p = *p_buf;
	while (1) {
		ret = fgets(p, *p_buf + *sizebuf - p, fin);
		if (!ret || strrchr(p, '\n'))
			break;
		*p_buf = xrealloc(*p_buf, *sizebuf + 3000);
		p = *p_buf + *sizebuf - 1;
		*sizebuf += 3000;
	}
	if (**p_buf)
		ret = *p_buf;
	return ret;
}

/*
 *	Read a line of a configuration file and process continuation lines.
 *	Return buf, or NULL if EOF.
 *	Blank at the end of line are always stripped.
 *	Everything on a line following comchar is a comment.
 *
 *	Continuation character is \
 *	Comment character is #
 */
char *fgets_strip(char **p_buf, int *sizebuf, FILE * fin, int *lineno)
{
	char *ret = NULL;
	char *buf;
	char *buf_prev = NULL;
	char comchar = '#';
	char contchar = '\\';


	while ((buf = fgets_local(p_buf, sizebuf, fin)) != NULL) {
		char *start = buf;
		char *end = strip_end(buf);
		char *pt = strchr(buf, comchar);

		if (pt != NULL) {
			*pt = '\0';
			end = strip_end(buf);
		}

		if (lineno != NULL)
			(*lineno)++;
		ret = start;
		if (buf_prev) {
			/* this is a continuation line, append to the previous
			 * buffer which becomes the current buffer.  White space
			 * at the start of the continuation line is replaced by
			 * a single space.
			 */
			int buf_prev_len = strlen(buf_prev);
			buf_prev = xrealloc(buf_prev, buf_prev_len + *sizebuf);
			memcpy(buf_prev + buf_prev_len, *p_buf, *sizebuf);
			*sizebuf += buf_prev_len;
			free(*p_buf);
			*p_buf = buf_prev;
			buf_prev = NULL;
			buf = *p_buf + buf_prev_len;
			pt = buf;
			start = buf;
			end = strip_end(buf);
			while (isspace(*pt))
				pt++;
			if (pt > buf + 1) {
				strcpy(buf + 1, pt);	/* safe, backward copy */
				buf[0] = ' ';
				end -= (int) (pt - buf) - 1;
			} else if (pt == buf + 1) {
				buf[0] = ' ';
			}
		}
		if (end > buf && *(end - 1) == contchar) {
			if (end == buf + 1 || *(end - 2) != contchar) {
				/* Continuation */
				end--;
				*end = '\0';
				buf_prev = *p_buf;
				*p_buf = NULL;
			} else {
				*(end - 1) = '\0';
				break;
			}
		} else {
			break;
		}
	}

	return ret;
}

static char *next_word(char *pt)
{
	char *match;
	char *pt2;

	/* find end of word */
	for (pt2 = pt; *pt2 && !(isspace(*pt2)); ++pt2) {
		if ((match = strchr("\"'`", *pt2)) != NULL) {
			for (++pt2; *pt2 && *pt2 != *match; ++pt2) {
				if (*pt2 == '\\' && *(pt2 + 1) == *match)
					++pt2;
			}
		}
	}

	/* skip leading whitespace before next word */
	if (*pt2) {
		*pt2++ = '\0'; /* terminate last word */
		while (*pt2 && isspace(*pt2))
			++pt2;
	}
	return pt2;
}

static GLOB_LIST *addlist(GLOB_LIST *orig, GLOB_LIST *add)
{
	if (!orig)
		return add;
	/* else */
	orig->pathv = (char **)xrealloc(orig->pathv,
					(orig->pathc + add->pathc + 1) *
					sizeof(char *));
	memcpy(orig->pathv + orig->pathc, add->pathv,
	       add->pathc * sizeof(char *));
	orig->pathc += add->pathc;
	orig->pathv[orig->pathc] = NULL;
	/*
	free(add->pathv);
	free(add);
	*/
	return orig;
}

static void decode_list(int *n, OPT_LIST **list, char *arg, int adding,
			char *version, int opts)
{
	GLOB_LIST *pg;
	GLOB_LIST *prevlist = NULL;
	int i, autoclean = 1;
	int where = *n;
	char *arg2 = next_word(arg);

	if (opts && !strcmp (arg, "-k")) {
		if (!*arg2)
			error("Missing module argument after -k\n");
		arg = arg2;
		arg2 = next_word(arg);
		autoclean = 0;
	}

	for (i = 0; i < *n; ++i) {
		if (strcmp((*list)[i].name, arg) == 0) {
			if (adding)
				prevlist = (*list)[i].opts;
			else
				free((*list)[i].opts);
			(*list)[i].opts = NULL;
			where = i;
			break;
		}
	}
	if (where == *n) {
		(*list) = (OPT_LIST *)xrealloc((*list),
			  (*n + 2) * sizeof(OPT_LIST));
		(*list)[*n].name = xstrdup(arg);
		(*list)[*n].autoclean = autoclean;
		*n += 1;
		memset(&(*list)[*n], 0, sizeof(OPT_LIST));
	} else if (!autoclean)
		(*list)[where].autoclean = 0;
	pg = (GLOB_LIST *)xmalloc(sizeof(GLOB_LIST));
	meta_expand(arg2, pg, NULL, version, ME_ALL);
	(*list)[where].opts = addlist(prevlist, pg);
}

static void decode_exec(char *arg, int type)
{
	char *arg2;

	execs[nexecs].when = type;
	arg2 = next_word(arg);
	execs[nexecs].module = xstrdup(arg);
	execs[nexecs].cmd = xstrdup(arg2);
	if (++nexecs >= maxexecs) {
		maxexecs += 10;
		execs = (struct EXEC_TYPE *)xrealloc(execs,
			maxexecs * sizeof(struct EXEC_TYPE));
	}
}

static int build_list(char **in, OPT_LIST **out, char *version, int opts)
{
	GLOB_LIST *pg;
	int i;

	for (i = 0; in[i]; ++i) {
		char *p = xstrdup(in[i]);
		char *pt = next_word(p);
		char *pn = p;

		*out = (OPT_LIST *)xrealloc(*out, (i + 2) * sizeof(OPT_LIST));
		(*out)[i].autoclean = 1;
		if (opts && !strcmp (p, "-k")) {
		    pn = pt;
		    pt = next_word(pn);
		    (*out)[i].autoclean = 0;
		}
		pg = (GLOB_LIST *)xmalloc(sizeof(GLOB_LIST));
		meta_expand(pt, pg, NULL, version, ME_ALL);
		(*out)[i].name = xstrdup(pn);
		(*out)[i].opts = pg;
		free(p);
	}
	memset(&(*out)[i], 0, sizeof(OPT_LIST));

	return i;
}

/* Environment variables can override defaults, testing only */
static void gen_file_env(struct gen_files *gf)
{
	if (!safemode) {
		char *e = xmalloc(strlen(gf->base)+5), *p1 = gf->base, *p2 = e;
		while ((*p2++ = toupper(*p1++))) ;
		strcpy(p2-1, "PATH");	/* safe, xmalloc */
		if ((p2 = getenv(e)) != NULL) {
			free(gf->name);
			gf->name = xstrdup(p2);
		}
		free(e);
	}
}

/* Read a config option for a generated filename */
static int gen_file_conf(struct gen_files *gf, int assgn, const char *parm, const char *arg)
{

	int l = strlen(gf->base);
	if (assgn &&
	    strncmp(parm, gf->base, l) == 0 &&
	    strcmp(parm+l, "file") == 0 &&
	    !gf->name) {
		gf->name = xstrdup(arg);
		return(0);
	}
	return(1);
}

/* Check we have a name for a generated file */
static int gen_file_check(struct gen_files *gf, GLOB_LIST *g,
			  char *base_dir, char *version)
{
	char tmp[PATH_MAX];
	int ret = 0;
	if (!gf->name) {
		/*
		 * Specification: config file / no xxxfile parameter
		 * The default value for generated filename xxx is:
		 *
		 * xxxfile=/lib/modules/`uname -r`/modules.xxx
		 *
		 * If the config file exists but lacks an xxxfile
		 * specification, the default value is used since
		 * the system can't work without one.
		 */
		snprintf(tmp, sizeof(tmp), "%s/lib/modules/%s/modules.%s",
			base_dir, version, gf->base);
		gf->name = xstrdup(tmp);
	} else { /* xxxfile defined in modules.conf */
		/*
		 * If we have a xxxfile definition in the configuration file
		 * we must resolve any shell meta-chars in its value.
		 */
		if (meta_expand(gf->name, g, base_dir, version, ME_ALL))
			ret = -1;
		else if (!g->pathv || g->pathv[0] == NULL)
			ret = -1;
		else {
			free(gf->name);
			gf->name = xstrdup(g->pathv[0]);
		}
	}
	return(ret);
}

/*
 *	Read the configuration file.
 *	If parameter "all" == 0 then ignore everything except path info
 *	Return -1 if any error.
 *	Error messages generated.
 */
static int do_read(int all, char *force_ver, char *base_dir, char *conf_file, int depth)
{
	#define MAX_LEVEL 20
	FILE *fin;
	GLOB_LIST g;
	int i;
	int assgn;
	int drop_default_paths = 1;
	int lineno = 0;
	int ret = 0;
	int state[MAX_LEVEL + 1]; /* nested "if" */
	int level = 0;
	int sizebuf;
	char *buf = NULL;
	char tmpline[PATH_MAX];
	char **pathp;
	char *envpath;
	char *version;
	char *type;
	char **glb;
	char old_name[] = "/etc/conf.modules";
	int conf_file_specified = 0;

	/*
	 * The configuration file is optional.
	 * No error is printed if it is missing.
	 * If it is missing the following content is assumed.
	 *
	 * path[boot]=/lib/modules/boot
	 *
	 * path[toplevel]=/lib/modules/`uname -r`
	 *
	 * path[toplevel]=/lib/modules/`kernelversion`
	 *   (where kernelversion gives the major kernel version: "2.0", "2.2"...)
	 *
	 * path[toplevel]=/lib/modules/default
	 *
	 * path[kernel]=/lib/modules/kernel
	 * path[fs]=/lib/modules/fs
	 * path[net]=/lib/modules/net
	 * path[scsi]=/lib/modules/scsi
	 * path[block]=/lib/modules/block
	 * path[cdrom]=/lib/modules/cdrom
	 * path[ipv4]=/lib/modules/ipv4
	 * path[ipv6]=/lib/modules/ipv6
	 * path[sound]=/lib/modules/sound
	 * path[fc4]=/lib/modules/fc4
	 * path[video]=/lib/modules/video
	 * path[misc]=/lib/modules/misc
	 * path[pcmcia]=/lib/modules/pcmcia
	 * path[atm]=/lib/modules/atm
	 * path[usb]=/lib/modules/usb
	 * path[ide]=/lib/modules/ide
	 * path[ieee1394]=/lib/modules/ieee1394
	 * path[mtd]=/lib/modules/mtd
	 *
	 * The idea is that modprobe will look first if the
	 * modules are compiled for the current release of the kernel.
	 * If not found, it will look for modules that fit for the
	 * general kernelversion (2.0, 2.2 and so on).
	 * If still not found, it will look into the default release.
	 * And if still not found, it will look in the other directories.
	 *
	 * The strategy should be like this:
	 * When you install a new linux kernel, the modules should go
	 * into a directory related to the release (version) of the kernel.
	 * Then you can do a symlink "default" to this directory.
	 *
	 * Each time you compile a new kernel, the make modules_install
	 * will create a new directory, but it won't change thee default.
	 *
	 * When you get a module unrelated to the kernel distribution
	 * you can place it in one of the last three directory types.
	 *
	 * This is the default strategy. Of course you can overide
	 * this in /etc/modules.conf.
	 *
	 * 2.3.15 added a new file tree walk algorithm which made it possible to
	 * point at a top level directory and get the same behaviour as earlier
	 * versions of modutils.  2.3.16 takes this one stage further, it
	 * removes all the individual directory names from most of the scans,
	 * only pointing at the top level directory.  The only exception is the
	 * last ditch scan, scanning all of /lib/modules would be a bad idea(TM)
	 * so the last ditch scan still runs individual directory names under
	 * /lib/modules.
	 *
	 * Additional syntax:
	 *
	 * [add] above module module1 ...
	 *	Specify additional modules to pull in on top of a module
	 *
	 * [add] below module module1 ...
	 *	Specify additional modules needed to be able to load a module
	 *
	 * [add] prune filename ...
	 *
	 * [add] probe name module1 ...
	 *	When "name" is requested, modprobe tries to install each
	 *	module in the list until it succeeds.
	 *
	 * [add] probeall name module1 ...
	 *	When "name" is requested, modprobe tries to install all
	 *	modules in the list.
	 *	If any module is installed, the command has succeeded.
	 *
	 * [add] options module option_list
	 *
	 * For all of the above, the optional "add" prefix is used to
	 * add to a list instead of replacing the contents.
	 *
	 * include FILE_TO_INCLUDE
	 *	This does what you expect. No limitation on include levels.
	 *
	 * persistdir=persist_directory
	 *	Name the directory to save persistent data from modules.
	 *
	 * In the following WORD is a sequence if non-white characters.
	 * If ' " or ` is found in the string, all characters up to the
	 * matching ' " or ` will also be included, even whitespace.
	 * Every WORD will then be expanded w.r.t. meta-characters.
	 * If the expanded result gives more than one word, then only
	 * the first word of the result will be used.
	 *
	 *
	 * define CODE WORD
	 *		Do a putenv("CODE=WORD")
	 *
	 * EXPRESSION below can be:
	 *	WORD compare_op WORD
	 *		where compare_op is one of == != < <= >= >
	 *		The string values of the WORDs are compared
	 * or
	 *	-n WORD compare_op WORD
	 *		where compare_op is one of == != < <= >= >
	 *		The numeric values of the WORDs are compared
	 * or
	 *	WORD
	 *		if the expansion of WORD fails, or if the
	 *		expansion is "0" (zero), "false" or "" (empty)
	 *		then the expansion has the value FALSE.
	 *		Otherwise the expansion has the value TRUE
	 * or
	 *	-f FILENAME
	 *		Test if the file FILENAME exists
	 * or
	 *	-k
	 *		Test if "autoclean" (i.e. called from the kernel)
	 * or
	 *	! EXPRESSION
	 *		A negated expression is also an expression
	 *
	 * if EXPRESSION
	 *	any config line
	 *	...
	 * elseif EXPRESSION
	 *	any config line
	 *	...
	 * else
	 *	any config line
	 *	...
	 * endif
	 *
	 * The else and elseif keywords are optional.
	 * "if"-statements nest up to 20 levels.
	 */

	state[0] = 1;

	if (force_ver)
		version = force_ver;
	else
		version = uts_info.release;

	config_version = xstrdup(version);

	/* Only read the default entries on the first file */
	if (depth == 0) {
		maxpath = 100;
		modpath = (struct PATH_TYPE *)xmalloc(maxpath * sizeof(struct PATH_TYPE));
		nmodpath = 0;

		maxexecs = 10;
		execs = (struct EXEC_TYPE *)xmalloc(maxexecs * sizeof(struct EXEC_TYPE));
		nexecs = 0;

		/*
		 * Build predef options
		 */
		if (all && optlist[0])
			n_opt_list = build_list(optlist, &opt_list, version, 1);

		/*
		 * Build predef above
		 */
		if (all && above[0])
			n_abovelist = build_list(above, &abovelist, version, 0);

		/*
		 * Build predef below
		 */
		if (all && below[0])
			n_belowlist = build_list(below, &belowlist, version, 0);

		/*
		 * Build predef prune list
		 */
		if (prune[0])
			n_prunelist = build_list(prune, &prunelist, version, 0);

		/*
		 * Build predef aliases
		 */
		if (all && aliaslist[0])
			n_aliases = build_list(aliaslist, &aliases, version, 0);
		
		/*
		 * Build predef postinstalls
		 */
		if (all && post_install[0])
			for (i=0 ; post_install[i] ; i++ )
				decode_exec(xstrdup(post_install[i]), EXEC_POST_INSTALL);
		
		/*
		 * Build predef preremoves
		 */
		if (all && pre_remove[0])
			for (i=0 ; pre_remove[i] ; i++ )
				decode_exec(xstrdup(pre_remove[i]), EXEC_PRE_REMOVE);

		/* Order and priority is now: (MODPATH + modules.conf) || (predefs + modules.conf) */
		if ((envpath = getenv("MODPATH")) != NULL && !safemode) {
			size_t len;
			char *p;
			char *path;

			/* Make a copy so's we can mung it with strtok.  */
			len = strlen(envpath) + 1;
			p = alloca(len);
			memcpy(p, envpath, len);
			path = alloca(PATH_MAX);

			for (p = strtok(p, ":"); p != NULL; p = strtok(NULL, ":")) {
				len = snprintf(path, PATH_MAX, p, version);
				modpath[nmodpath].path = xstrdup(path);
				if ((type = strrchr(path, '/')) != NULL)
					type += 1;
				else
					type = "misc";
				modpath[nmodpath].type = xstrdup(type);
				if (++nmodpath >= maxpath) {
					maxpath += 100;
					modpath = (struct PATH_TYPE *)xrealloc(modpath,
						maxpath * sizeof(struct PATH_TYPE));
				}

			}
		} else {
			/*
			 * Build the default "path[type]" configuration
			 */
			int n;
			char *k;

			/* The first entry in the path list */
			modpath[nmodpath].type = xstrdup("boot");
			snprintf(tmpline, sizeof(tmpline), "%s/lib/modules/boot", base_dir);
			modpath[nmodpath].path = xstrdup(tmpline);
			++nmodpath;

			/* The second entry in the path list, `uname -r` */
			modpath[nmodpath].type = xstrdup("toplevel");
			snprintf(tmpline, sizeof(tmpline), "%s/lib/modules/%s", base_dir, version);
			modpath[nmodpath].path = xstrdup(tmpline);
			++nmodpath;

			/* The third entry in the path list, `kernelversion` */
			modpath[nmodpath].type = xstrdup("toplevel");
			for (n = 0, k = version; *k; ++k) {
				if (*k == '.' && ++n == 2)
					break;
			}
			snprintf(tmpline, sizeof(tmpline), "%s/lib/modules/%.*s", base_dir,
				(/* typecast for Alpha */ int)(k - version), version);
			modpath[nmodpath].path = xstrdup(tmpline);
			++nmodpath;

			/* The rest of the entries in the path list */
			for (pathp = tbpath; *pathp; ++pathp) {
				char **type;

				for (type = tbtype; *type; ++type) {
					char path[PATH_MAX];

					snprintf(path, sizeof(path), "%s%s/%s", base_dir, *pathp, *type);
					if (meta_expand(path, &g, NULL, version, ME_ALL)) {
						ret = -1;
						goto out;
					}

					for (glb = g.pathv; glb && *glb; ++glb) {
						modpath[nmodpath].type = xstrdup(*type);
						modpath[nmodpath].path = *glb;
						if (++nmodpath >= maxpath) {
							maxpath += 100;
							modpath = (struct PATH_TYPE *)xrealloc(modpath,
								maxpath * sizeof(struct PATH_TYPE));
						}
					}
				}
			}
		}

		/* Environment overrides for testing only, undocumented */
		for (i = 0; i < gen_file_count; ++i)
			gen_file_env(gen_file+i);

	}	/* End of depth == 0 */

	if (conf_file ||
	    ((conf_file = getenv("MODULECONFIG")) != NULL && *conf_file && !safemode)) {
		if (!(fin = fopen(conf_file, "r"))) {
			error("Can't open %s", conf_file);
			ret = -1;
			goto out;
		}
		conf_file_specified = 1;
	} else {
		if (!(fin = fopen((conf_file = ETC_MODULES_CONF), "r"))) {
			/* Fall back to non-standard name */
			if ((fin = fopen((conf_file = old_name), "r"))) {
				fprintf(stderr,
					"Warning: modutils is reading from %s because\n"
					"         %s does not exist.  The use of %s is\n"
					"         deprecated, please rename %s to %s\n"
					"         as soon as possible.  Command\n"
					"         mv %s %s\n",
					old_name, ETC_MODULES_CONF,
					old_name, old_name, ETC_MODULES_CONF,
					old_name, ETC_MODULES_CONF);
			}
			/* So what... use the default configuration */
		}
	}

	if (fin) {
		struct stat statbuf1, statbuf2;
		if (fstat(fileno(fin), &statbuf1) == 0)
			config_mtime = statbuf1.st_mtime;
		config_file = xstrdup(conf_file);	/* Save name actually used */
		if (!conf_file_specified &&
		    stat(ETC_MODULES_CONF, &statbuf1) == 0 &&
		    stat(old_name, &statbuf2) == 0) {
			/* Both /etc files exist */
			if (statbuf1.st_dev == statbuf2.st_dev &&
			    statbuf1.st_ino == statbuf2.st_ino) {
				if (lstat(ETC_MODULES_CONF, &statbuf1) == 0 &&
				    S_ISLNK(statbuf1.st_mode))
					fprintf(stderr,
						"Warning: You do not need a link from %s to\n"
						"         %s.  The use of %s is deprecated,\n"
						"         please remove %s and rename %s\n"
						"         to %s as soon as possible.  Commands.\n"
						"           rm %s\n"
						"           mv %s %s\n",
						ETC_MODULES_CONF, old_name,
						old_name, ETC_MODULES_CONF, old_name, ETC_MODULES_CONF,
						ETC_MODULES_CONF,
						old_name, ETC_MODULES_CONF);
				else {
#ifndef NO_WARN_ON_OLD_LINK
					fprintf(stderr,
						"Warning: You do not need a link from %s to\n"
						"         %s.  The use of %s is deprecated,\n"
						"         please remove %s as soon as possible.  Command\n"
						"           rm %s\n",
						old_name, ETC_MODULES_CONF,
						old_name, old_name,
						old_name);
#endif
				}
			}
			else
				fprintf(stderr,
					"Warning: modutils is reading from %s and\n"
					"         ignoring %s.  The use of %s is deprecated,\n"
					"         please remove %s as soon as possible.  Command\n"
					"           rm %s\n",
					ETC_MODULES_CONF, old_name,
					old_name, old_name,
					old_name);
		}
	}

	/*
	 * Finally, decode the file
	 */
	while (fin && fgets_strip(&buf, &sizebuf, fin, &lineno) != NULL) {
		char *arg2;
		char *parm = buf;
		char *arg;
		int one_err = 0;
		int adding;

		while (isspace(*parm))
			parm++;

		if (strncmp(parm, "add", 3) == 0) {
			adding = 1;
			parm += 3;
			while (isspace(*parm))
				parm++;
		} else
			adding = 0;

		arg = parm;

		if (*parm == '\0')
			continue;

		one_err = 1;

		while (*arg > ' ' && *arg != '=')
			arg++;

		if (*arg == '=')
			assgn = 1;
		else
			assgn = 0;
		*arg++ = '\0';
		while (isspace(*arg))
			arg++;

		/*
		 * endif
		 */
		if (!assgn && strcmp(parm, "endif") == 0) {
			if (level > 0)
				--level;
			else {
				error("unmatched endif in line %d", lineno);
				ret = -1;
				goto out;
			}
			continue;
		}

		/*
		 * else
		 */
		if (!assgn && strcmp(parm, "else") == 0) {
			if (level <= 0) {
				error("else without if in line %d", lineno);
				ret = -1;
				goto out;
			}
			state[level] = !state[level];
			continue;
		}

		/*
		 * elseif
		 */
		if (!assgn && strcmp(parm, "elseif") == 0) {
			if (level <= 0) {
				error("elseif without if in line %d", lineno);
				ret = -1;
				goto out;
			}
			if (state[level] != 0) {
				/*
				 * We have already found a TRUE
				 * if statement in this "chain".
				 * That's what "2" means.
				 */
				state[level] = 2;
				continue;
			}
			/* else: No TRUE if has been found, cheat */
			/*
			 * The "if" handling increments level,
			 * but this is the _same_ level as before.
			 * So, compensate for it.
			 */
			--level;
			parm = "if";
			/* Fallthru to "if" */
		}

		/*
		 * if
		 */
		if (strcmp(parm, "if") == 0) {
			char *cmp;
			int not = 0;
			int numeric = 0;

			if (level >= MAX_LEVEL) {
				error("Too many nested if's in line %d\n", lineno);
				ret = -1;
				goto out;
			}
			state[++level] = 0; /* default false */

			if (*arg == '!') {
				not = 1;
				arg = next_word(arg);
			}

			if (strncmp(arg, "-k", 2) == 0) {
				state[level] = flag_autoclean;
				continue;
			}

			if (strncmp(arg, "-f", 2) == 0) {
				char *file = next_word(arg);
				meta_expand(file, &g, NULL, version, ME_ALL);
				if (access(g.pathc ? g.pathv[0] : file, R_OK) == 0)
					state[level] = !not;
				else
					state[level] = not;
				continue;
			}

			if (strncmp(arg, "-n", 2) == 0) {
				numeric = 1;
				arg = next_word(arg);
			}


			cmp = next_word(arg);
			if (*cmp) {
				GLOB_LIST g2;
				long n1 = 0;
				long n2 = 0;
				char *w1 = "";
				char *w2 = "";

				arg2 = next_word(cmp);

				meta_expand(arg, &g, NULL, version, ME_ALL);
				if (g.pathc && g.pathv[0])
					w1 = g.pathv[0];

				meta_expand(arg2, &g2, NULL, version, ME_ALL);
				if (g2.pathc && g2.pathv[0])
					w2 = g2.pathv[0];

				if (numeric) {
					n1 = strtol(w1, NULL, 0);
					n2 = strtol(w2, NULL, 0);
				}

				if (strcmp(cmp, "==") == 0 ||
				    strcmp(cmp, "=") == 0) {
					if (numeric)
					    state[level] = (n1 == n2);
					else
					    state[level] = strcmp(w1, w2) == 0;
				} else if (strcmp(cmp, "!=") == 0) {
					if (numeric)
					    state[level] = (n1 != n2);
					else
					    state[level] = strcmp(w1, w2) != 0;
				} else if (strcmp(cmp, ">=") == 0) {
					if (numeric)
					    state[level] = (n1 >= n2);
					else
					    state[level] = strcmp(w1, w2) >= 0;
				} else if (strcmp(cmp, "<=") == 0) {
					if (numeric)
					    state[level] = (n1 <= n2);
					else
					    state[level] = strcmp(w1, w2) <= 0;
				} else if (strcmp(cmp, ">") == 0) {
					if (numeric)
					    state[level] = (n1 > n2);
					else
					    state[level] = strcmp(w1, w2) > 0;
				} else if (strcmp(cmp, "<") == 0) {
					if (numeric)
					    state[level] = (n1 < n2);
					else
					    state[level] = strcmp(w1, w2) < 0;
				}
			} else { /* Check defined value, if any */
				/* undef or defined as
				 *	"" or "0" or "false" => false
				 *  defined => true
				 */
				if (!meta_expand(arg, &g, NULL, version, ME_ALL) &&
				    g.pathc > 0 &&
				    strcmp(g.pathv[0], "0") != 0 &&
				    strcmp(g.pathv[0], "false") != 0 &&
				    strlen(g.pathv[0]) != 0)
					state[level] = 1; /* true */
			}
			if (not)
				state[level] = !state[level];

			continue;
		}

		/*
		 * Should we bother?
		 */
		if (state[level] != 1)
			continue;

		/*
		 * define
		 */
		if (!assgn && strcmp(parm, "define") == 0) {
			char env[PATH_MAX];

			arg2 = next_word(arg);
			meta_expand(arg2, &g, NULL, version, ME_ALL);
			snprintf(env, sizeof(env), "%s=%s", arg, (g.pathc ? g.pathv[0] : ""));
			putenv(xstrdup(env));
			one_err = 0;
		}

		/*
		 * include
		 */
		if (!assgn && strcmp(parm, "include") == 0) {
			int inc_idx = 0;

			while (*arg) {
				arg2=next_word(arg);
				meta_expand(arg, &g, NULL, version, ME_ALL);

				if (g.pathc>0) {
					for ( ; inc_idx<g.pathc; inc_idx++) {
						if (!do_read(all, version, base_dir, g.pathv[inc_idx], depth+1))
							one_err = 0;
						else
							error("include %s failed\n", g.pathv[inc_idx]);
					}
				}
				else {
					if (!do_read(all, version, base_dir, g.pathc ? g.pathv[0] : arg, depth+1))
						one_err = 0;
					else
						error("include %s failed\n", arg);
				}

				arg = arg2;
			}
		}

		/*
		 * above
		 */
		else if (all && !assgn && strcmp(parm, "above") == 0) {
			decode_list(&n_abovelist, &abovelist, arg, adding, version, 0);
			one_err = 0;
		}

		/*
		 * below
		 */
		else if (all && !assgn && strcmp(parm, "below") == 0) {
			decode_list(&n_belowlist, &belowlist, arg, adding, version, 0);
			one_err = 0;
		}

		/*
		 * prune
		 */
		else if (!assgn && strcmp(parm, "prune") == 0) {
			decode_list(&n_prunelist, &prunelist, arg, adding, version, 0);
			one_err = 0;
		}

		/*
		 * probe
		 */
		else if (all && !assgn && strcmp(parm, "probe") == 0) {
			decode_list(&n_probe_list, &probe_list, arg, adding, version, 0);
			one_err = 0;
		}

		/*
		 * probeall
		 */
		else if (all && !assgn && strcmp(parm, "probeall") == 0) {
			decode_list(&n_probeall_list, &probeall_list, arg, adding, version, 0);
			one_err = 0;
		}

		/*
		 * options
		 */
		else if (all && !assgn && strcmp(parm, "options") == 0) {
			decode_list(&n_opt_list, &opt_list, arg, adding, version, 1);
			one_err = 0;
		}

		/*
		 * alias
		 */
		else if (all && !assgn && strcmp(parm, "alias") == 0) {
			/*
			 * Replace any previous (default) definitions
			 * for the same module
			 */
			decode_list(&n_aliases, &aliases, arg, 0, version, 0);
			one_err = 0;
		}

		/*
		 * Specification: /etc/modules.conf
		 * The format of the commands in /etc/modules.conf are:
		 *
		 *	pre-install module command
		 *	install module command
		 *	post-install module command
		 *	pre-remove module command
		 *	remove module command
		 *	post-remove module command
		 *
		 * The different words are separated by tabs or spaces.
		 */
		/*
		 * pre-install
		 */
		else if (all && !assgn && (strcmp(parm, "pre-install") == 0)) {
			decode_exec(arg, EXEC_PRE_INSTALL);
			one_err = 0;
		}

		/*
		 * install
		 */
		else if (all && !assgn && (strcmp(parm, "install") == 0)) {
			decode_exec(arg, EXEC_INSTALL);
			one_err = 0;
		}

		/*
		 * post-install
		 */
		else if (all && !assgn && (strcmp(parm, "post-install") == 0)) {
			decode_exec(arg, EXEC_POST_INSTALL);
			one_err = 0;
		}

		/*
		 * pre-remove
		 */
		else if (all && !assgn && (strcmp(parm, "pre-remove") == 0)) {
			decode_exec(arg, EXEC_PRE_REMOVE);
			one_err = 0;
		}

		/*
		 * remove
		 */
		else if (all && !assgn && (strcmp(parm, "remove") == 0)) {
			decode_exec(arg, EXEC_REMOVE);
			one_err = 0;
		}

		/*
		 * post-remove
		 */
		else if (all && !assgn && (strcmp(parm, "post-remove") == 0)) {
			decode_exec(arg, EXEC_POST_REMOVE);
			one_err = 0;
		}

		/*
		 * insmod_opt=
		 */
		else if (assgn && (strcmp(parm, "insmod_opt") == 0)) {
			insmod_opt = xstrdup(arg);
			one_err = 0;
		}

		/*
		 * keep
		 */
		else if (!assgn && (strcmp(parm, "keep") == 0)) {
			drop_default_paths = 0;
			one_err = 0;
		}

		/*
		 * path...=
		 */
		else if (assgn && strncmp(parm, "path", 4) == 0) {
			/*
			 * Specification: config file / path parameter
			 * The path parameter specifies a directory to
			 * search for modules.
			 * This parameter may be repeated multiple times.
			 *
			 * Note that the actual path may be defined using
			 * wildcards and other shell meta-chars, such as "*?`".
			 * For example:
			 *      path[misc]=/lib/modules/1.1.5?/misc
			 *
			 * Optionally the path keyword carries a tag.
			 * This tells us a little more about the purpose of
			 * this directory and allows some automated operations.
			 * A path is marked with a tag by adding the tag,
			 * enclosed in square brackets, to the path keyword:
			 * #
			 * path[boot]=/lib/modules/boot
			 * #
			 * This case identifies the path a of directory
			 * holding modules loadable a boot time.
			 */

			if (drop_default_paths) {
				int n;

				/*
				 * Specification: config file / path / default
				 *
				 * Whenever there is a path[] specification
				 * in the config file, all the default
				 * path are reset.
				 *
				 * If one instead wants to _add_ to the default
				 * set of paths, one has to have the option
				 *    keep
				 * before the first path[]-specification line
				 * in the configuration file.
				 */
				drop_default_paths = 0;
				for (n = 0; n < nmodpath; n++) {
					free(modpath[n].path);
					free(modpath[n].type);
				}
				nmodpath = 0;
			}

			/*
			 * Get (the optional) tag
			 * If the tag is missing, the word "misc"
			 * is assumed.
			 */
			type = "misc";

			if (parm[4] == '[') {
				char *pt_type = parm + 5;

				while (*pt_type != '\0' && *pt_type != ']')
					pt_type++;

				if (*pt_type == ']' && pt_type[1] == '\0') {
					*pt_type = '\0';
					type = parm + 5;
				} /* else CHECKME */
			}

			/*
			 * Handle the actual path description
			 */
			if (meta_expand(arg, &g, base_dir, version, ME_ALL)) {
				ret = -1;
				goto out;
			}
			for (glb = g.pathv; glb && *glb; ++glb) {
				modpath[nmodpath].type = xstrdup(type);
				modpath[nmodpath].path = *glb;
				if (++nmodpath >= maxpath) {
					maxpath += 100;
					modpath = (struct PATH_TYPE *)xrealloc(modpath,
						maxpath * sizeof(struct PATH_TYPE));
				}
			}
			one_err = 0;
		}

		/*
		 * persistdir
		 */
		else if (assgn && strcmp(parm, "persistdir") == 0) {
			meta_expand(arg, &g, NULL, version, ME_ALL);
			persistdir = xstrdup(g.pathc ? g.pathv[0] : arg);
			one_err = 0;
		}

		/* Names for generated files in config file */
		for (i = 0; one_err && i < gen_file_count; ++i)
			one_err = gen_file_conf(gen_file+i, assgn, parm, arg);

		/*
		 * any errors so far?
		 */
		if (all == 0)
			one_err = 0;
		else if (one_err) {
			error("Invalid line %d in %s\n\t%s",
				     lineno, conf_file, buf);
			ret = -1;
		}
	}
	if (fin)
		fclose(fin);

	if (level) {
		error("missing endif at %s EOF", conf_file);
		ret = -1;
	}

	if (ret)
		goto out;
	/* else */

	if (depth == 0) {
		/* Check we have names for generated files */
		for (i = 0; !ret && i < gen_file_count; ++i)
			ret = gen_file_check(gen_file+i, &g, base_dir, version);
	}

out:
	return ret;
}

int config_read(int all, char *force_ver, char *base_dir, char *conf_file)
{
	int r;
	if (modpath != NULL)
		return 0; /* already initialized */

	if (uname(&uts_info) < 0) {
		error("Failed to find kernel name information");
		return -1;
	}

	if (access(MODUTILS_MACROS, R_OK) == 0){
		r = do_read(all, force_ver, base_dir, MODUTILS_MACROS, 0);
		r = do_read(all, force_ver, "", conf_file, 1);
	} else {
		r = do_read(all, force_ver, base_dir, conf_file, 0);
	} 

	if (quick && !r && !need_update (force_ver, base_dir))
		exit (0);

	return r;
}

/****************************************************************************/
/*
 *	FIXME: Far too much global state.  KAO.
 */
static int found;
static int favail;
static int one_only;
static int meta_expand_type;
char **list;
static const char *filter_by_file;
static char *filter_by_dir;

/*
 *	Add a file name if it exist
 */
static int config_add(const char *file, const struct stat *sb)
{
	int i;
	int npaths = 0;
	char **paths = NULL;

	if (meta_expand_type) {
		GLOB_LIST g;
		char **p;
		char full[PATH_MAX];

		snprintf(full, sizeof(full), "%s/%s", file, filter_by_file);

		if (filter_by_dir && !strstr(full, filter_by_dir))
			return 0;

		if (meta_expand(full, &g, NULL, config_version, meta_expand_type))
			return 1;
		for (p = g.pathv; p && *p; ++p) {
			paths = (char **)xrealloc(paths,
					(npaths + 1) * sizeof(char *));
			paths[npaths++] = *p;
		}
	} else { /* normal path match or match with "*" */
		if (!S_ISREG(sb->st_mode))
			return 0;

		if (strcmp(filter_by_file, "*")) {
			char *p;

			if ((p = strrchr(file, '/')) == NULL)
				p = (char *)file;
			else
				p += 1;

			if (strcmp(p, filter_by_file))
				return 0;
		}
		if (filter_by_dir && !strstr(file, filter_by_dir))
			return 0;
		paths = (char **)xmalloc(sizeof(char **));
		*paths = xstrdup(file);
		npaths = 1;
	}

	for (i = 0; i < npaths; ++i) {
		struct stat sbuf;

		if (S_ISDIR(sb->st_mode)) {
			if (stat(paths[i], &sbuf) == 0)
				sb = &sbuf;
		}
		if (S_ISREG(sb->st_mode) && sb->st_mode & S_IRUSR) {
			int j;
			char **this;

			if (!root_check_off) {
				if (sb->st_uid != 0) {
					error("%s is not owned by root", paths[i]);
					continue;
				}
			}

			/* avoid duplicates */
			for (j = 0, this = list; j < found; ++j, ++this) {
				if (strcmp(*this, paths[i]) == 0) {
					free(paths[i]);
					goto next;
				}
			}

			list[found] = paths[i];
			if (++found >= favail)
				list = (char **)xrealloc(list,
				     (favail += 100) * sizeof(char *));

			if (one_only) {
				for (j = i + 1; j < npaths; ++j)
					free(paths[j]);
				free(paths);
				return 1; /* finish xftw */
			}
		}
	    next: ;
	}

	if (npaths > 0)
		free(paths);

	return 0;
}

/*
 * Find modules matching the name "match" in directory of type "type"
 * (type == NULL matches all)
 *
 * Return a pointer to the list of modules found (or NULL if error).
 * Update the counter (sent as parameter).
 */
GLOB_LIST *config_lstmod(const char *match, const char *type, int first_only)
{
	/*
	 * Note:
	 * There are _no_ wildcards remaining in the path descriptions!
	 */
	struct stat sb;
	int i;
	int ret = 0;
	char *path = NULL;
	char this[PATH_MAX];

	if (!match)
		match = "*";
	one_only = first_only;
	found = 0;
	filter_by_file = match;
	filter_by_dir = NULL;
	if (type) {
		char tmpdir[PATH_MAX];
		snprintf(tmpdir, sizeof(tmpdir), "/%s/", type);
		filter_by_dir = xstrdup(tmpdir);
	}
	/* In safe mode, the module name is always handled as is, without meta
	 * expansion.  It might have come from an end user via kmod and must
	 * not be trusted.  Even in unsafe mode, only apply globbing to the
	 * module name, not command expansion.  We trust config file input so
	 * applying command expansion is safe, we do not trust command line input.
	 * This assumes that the only time the user can specify -C config file
	 * is when they run under their own authority.  In particular all
	 * mechanisms that call modprobe as root on behalf of the user must
	 * run in safe mode, without letting the user supply a config filename.
	 */
	meta_expand_type = 0;
	if (strpbrk(match, SHELL_META) && strcmp(match, "*") && !safemode)
		meta_expand_type = ME_GLOB|ME_BUILTIN_COMMAND;

	list = (char **)xmalloc((favail = 100) * sizeof(char *));

	for (i = 0; i < nmodpath; i++) {
		path = modpath[i].path;
		/* Special case: insmod: handle single, non-wildcard match */
		if (first_only && strpbrk(match, SHELL_META) == NULL) {
			/* Fix for "2.1.121 syntax */
			snprintf(this, sizeof(this), "%s/%s/%s", path,
						  modpath[i].type, match);
			if (stat(this, &sb) == 0 &&
			    config_add(this, &sb))
				break;
			/* End fix for "2.1.121 syntax */

			snprintf(this, sizeof(this), "%s/%s", path, match);
			if (stat(this, &sb) == 0 &&
			    config_add(this, &sb))
				break;
		}

		/* Start looking */
		if ((ret = xftw(path, config_add))) {
			break;
		}
	}
	if (ret >= 0) {
		GLOB_LIST *g = (GLOB_LIST *)xmalloc(sizeof(GLOB_LIST));
		g->pathc = found;
		g->pathv = list;
		free(filter_by_dir);
		return g;
	}
	free(list);
	free(filter_by_dir);
	return NULL;
}

/* Given a bare module name, poke through the module path to find the file.  */
char *search_module_path(const char *base)
{
	GLOB_LIST *g;

	if (config_read(0, NULL, "", NULL) < 0)
		return NULL;
	/* else */
	g = config_lstmod(base, NULL, 1);
	if (g == NULL || g->pathc == 0) {
		char base_o[PATH_MAX];

		snprintf(base_o, sizeof(base_o), "%s.o", base);
		g = config_lstmod(base_o, NULL, 1);
#ifdef CONFIG_USE_ZLIB
		if (g == NULL || g->pathc == 0) {
			snprintf(base_o, sizeof(base_o), "%s.o.gz", base);
			g = config_lstmod(base_o, NULL, 1);
		}
#endif
	}
	if (g == NULL || g->pathc == 0)
		return NULL;
	/* else */
	return g->pathv[0];
}
