/*========================================================================*\

Copyright (c) 2000  Paul Vojta

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following condition:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
PAUL VOJTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

\*========================================================================*/

/*
 *	Not done yet:
 *		nchoice
 *		text
 */

#define	VERSION	"0.92"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>

#ifndef ARCH
# if __alpha || __alpha__
#  define ARCH  "alpha"
# elif __arm || __arm__
#  define ARCH  "arm"
# elif __i386 || __i386__
#  define ARCH  "i386"
# elif __ia64 || __ia64__
#  define ARCH  "ia64"
# elif __m68k || __m68k__
#  define ARCH  "m68k"
# elif __mips64 || __mips64__
#  define ARCH  "mips64"
# elif __mips || __mips__
#  define ARCH  "mips"
# elif __ppc || __ppc__
#  define ARCH  "ppc"
# elif __s390 || __s390__
#  define ARCH  "s390"
# elif __sh || __sh__
#  define ARCH  "sh"
# elif __sparc64 || __sparc64__
#  define ARCH  "sparc64"
# elif __sparc || __sparc__
#  define ARCH  "sparc"
# else
#  error  Which architecture?
# endif
#endif

#define	NUMBER(x)	(sizeof (x) / sizeof *(x))

#define	NORETURN volatile

typedef	int	Boolean;
#define	True	1
#define	False	0

/*
 *	Command-line arguments
 */

const char	*path_qc_in	= "qconfig.in";
const char	*path_qc_out	= "qconfig.out";
const char	*path_dot_cfg	= ".config";
const char	*path_autoconf	= "include/linux/autoconf.h";
char		*path_defaults	= NULL;
char		*path_config_in	= NULL;
const char	*arch		= NULL;
const char	*trace_var	= NULL;
int		trace_var_len	= 0;

struct option {
	const char	*longname;
	short		shortname;
	void		*addr;
};

static	const struct option	options[]	= {
		{"input",	'i',	&path_qc_in},
		{"output",	'o',	&path_qc_out},
		{"config",	'c',	&path_dot_cfg},
		{"headers",	'h',	&path_autoconf},
		{"defaults",	'd',	&path_defaults},
		{"script",	's',	&path_config_in},
		{"arch",	'a',	&arch},
		{"trace",	't',	&trace_var},
		{"nominal",	'n',	NULL},
		{"version",	'v',	NULL},
		{"help",	'-',	NULL}};

const char	*path_tmp_out	= NULL;
const char	*path_tmp_config= NULL;
const char	*path_tmp_header= NULL;

#define	INCR	80
#define	ARGC_INCR 10

/*
 *	Global information
 */

   /* Current file.  */

FILE		*f;
int		lineno;
int		lineno_begin;
const char	*filename;

   /* Other files.  */
FILE		*out_file;	/* qconfig.out */
FILE		*config_file;	/* .config */
FILE		*header_file;	/* config.h */


/*
 *	My usual utilities.
 */

NORETURN void
clean_exit(void)
{
	if (out_file != NULL) {
	    fclose(out_file);
	    if (path_tmp_out != NULL)
		unlink(path_tmp_out);
	}

	if (config_file != NULL) {
	    fclose(config_file);
	    if (path_tmp_config != NULL)
		unlink(path_tmp_config);
	}

	if (header_file != NULL) {
	    fclose(header_file);
	    if (path_tmp_header != NULL)
		unlink(path_tmp_header);
	}

	exit(1);
}

NORETURN void
oops(const char *message, ...)
{
	va_list	args;

	va_start(args, message);
	fputs("qconfig: ", stderr);
	vfprintf(stderr, message, args);
	va_end(args);
	putc('\n', stderr);
	clean_exit();
}

NORETURN void
opt_oops(const char *message, ...)
{
	va_list	args;

	va_start(args, message);
	fputs("qconfig: ", stderr);
	vfprintf(stderr, message, args);
	va_end(args);
	fputs("\nTry `qconfig --help' for more information.\n", stderr);
	clean_exit();
}

NORETURN void
line_oops(const char *message, ...)
{
	va_list args;

	va_start(args, message);
	if (lineno == lineno_begin)
	    fprintf(stderr, "qconfig: %s line %d: ", filename, lineno);
	else
	    fprintf(stderr, "qconfig: %s lines %d-%d: ", filename, lineno_begin,
	      lineno);
	vfprintf(stderr, message, args);
	va_end(args);
	putc('\n', stderr);
	clean_exit();
}


static void *
xmalloc(unsigned size)
{
	void *mem = (void *) malloc(size);

	if (mem == NULL)
	    oops("Cannot allocate %u bytes.\n", size);
	return mem;
}

static void *
xrealloc(void *oldp, unsigned size)
{
	void	*mem;

	mem = oldp == NULL ? (void *) malloc(size)
	    : (void *) realloc(oldp, size);
	if (mem == NULL)
	    oops("Cannot reallocate %u bytes.\n", size);
	return mem;
}

static char *
xstrdup(const char *p)
{
	unsigned	len	= strlen(p) + 1;

	return memcpy(xmalloc(len), p, len);
}


/*
 *	Storing the shell variables.
 *	The algorithm is the AVL algorithm from Knuth Vol. 3.
 */

struct var {		/* data structure for storing "shell" variables */
	const char	*var_name;	/* key */
	int		len;		/* length of key */
	const char	*value;		/* value of variable */
	Boolean		accessed;	/* if it's been accessed yet */
	int		bal;		/* AVL balancing information */
	struct var	*left;
	struct var	*right;
};

struct var	*var_head;
struct var	*var_config_modules;
#define		config_modules()	(var_config_modules->accessed = True, \
					  *var_config_modules->value == 'y')

static struct var *
find_var(const char *var_name, int len)
{
	struct var	*tp;
	struct var	**tpp;
	struct var	*sp;	/* place where rebalancing may be necessary */
	struct var	**spp;	/* points to sp */
	int		i;

	/* Search */
	spp = tpp = &var_head;
	for (;;) {
	    tp = *tpp;
	    if (tp == NULL)	/* bottom of tree */
		break;
	    if (tp->bal != 0)
		spp = tpp;
	    i = len - tp->len;
	    if (i == 0)
		i = memcmp(var_name, tp->var_name, len);
	    if (i == 0)		/* found record already */
		return tp;
	    if (i < 0)		/* move left */
		tpp = &tp->left;
	    else
		tpp = &tp->right;
	}

	/* Insert */
	tp = xmalloc(sizeof *tp);
	tp->var_name = memcpy(xmalloc(len), var_name, len);
	tp->len = len;
	tp->value = NULL;
	tp->accessed = False;
	tp->bal = 0;
	tp->left = tp->right = NULL;
	*tpp = tp;

	/* Adjust balance factors */
	sp = *spp;
	if (sp == tp)
	    return tp;
	i = len - sp->len;
	if (i == 0)
	    i = memcmp(var_name, sp->var_name, len);
	sp = (i < 0 ? sp->left : sp->right);
	while (sp != tp) {
	    i = len - sp->len;
	    if (i == 0)
		i = memcmp(var_name, sp->var_name, len);
	    if (i < 0) {
		sp->bal = -1;
		sp = sp->left;
	    }
	    else {
		sp->bal = 1;
		sp = sp->right;
	    }
	}

	/* Balancing act */
	sp = *spp;
	i = len - sp->len;
	if (i == 0)
	    i = memcmp(var_name, sp->var_name, len);
	if (i < 0) {
	    if (sp->bal >= 0)
		--sp->bal;
	    else {	/* need to rebalance */
		struct var *left;

		left = sp->left;
		if (left->bal < 0) {	/* single rotation */
		    sp->left = left->right;
		    left->right = sp;
		    sp->bal = left->bal = 0;
		    *spp = left;
		}
		else {			/* double rotation */
		    struct var	*newtop;

		    newtop = left->right;
		    sp->left = newtop->right;
		    newtop->right = sp;
		    left->right = newtop->left;
		    newtop->left = left;
		    sp->bal = left->bal = 0;
		    if (newtop->bal < 0) ++sp->bal;
		    else if (newtop->bal > 0) --left->bal;
		    newtop->bal = 0;
		    *spp = newtop;
		}
	    }
	}
	else {
	    if (sp->bal <= 0)
		++sp->bal;
	    else {	/* need to rebalance */
		struct var *right;

		right = sp->right;
		if (right->bal > 0) {	/* single rotation */
		    sp->right = right->left;
		    right->left = sp;
		    sp->bal = right->bal = 0;
		    *spp = right;
		}
		else {			/* double rotation */
		    struct var	*newtop;

		    newtop = right->left;
		    sp->right = newtop->left;
		    newtop->left = sp;
		    right->left = newtop->right;
		    newtop->right = right;
		    sp->bal = right->bal = 0;
		    if (newtop->bal > 0) --sp->bal;
		    else if (newtop->bal < 0) ++right->bal;
		    newtop->bal = 0;
		    *spp = newtop;
		}
	    }
	}

	return tp;
}


static void
define_var(const char *str, int len, const char *value)
{
	struct var	*vp;

	vp = find_var(str, len);
	if (vp->value != NULL)
	    free((char *) vp->value);
	vp->value = xstrdup(value);
	if (len == trace_var_len && memcmp(str, trace_var, len) == 0)
	    printf("Variable %s modified at %s:%d\n", trace_var,
	      filename, lineno);
	if (vp->accessed) {
	    fputs("Warning:  variable ", stdout);
	    fwrite(str, 1, len, stdout);
	    puts(" modified after being used.");
	}
}


static const char *
lookup_var(const char *str, int len)
{
	return find_var(str, len)->value;
}


/*
 *	Reading from the file.
 */

char		*line;
unsigned	line_max;
const char	*lp;

static Boolean
getline(void)
{
	unsigned	len;

	++lineno;
	lp = line;
	for (len = 0;;) {
	    if (fgets(line + len, line_max - len, f) == NULL) {
		line[len] = '\0';
		return (len > 0);
	    }
	    len += strlen(line + len);
	    if (len > 0 && line[len - 1] == '\n') {
		line[--len] = '\0';
		break;
	    }
	    if (len == line_max - 1)
		lp = line = xrealloc(line, line_max += INCR);
	}
	return True;
}


int		argc;		/* The usual */
char		**argv;

char		*args;		/* Area for storing args */
unsigned	args_max;	/* size of that area */
char		*args_end;	/* args + args_max */
char		*args_pos;	/* where we are now */

unsigned	argc_max;	/* maximum value of argc */
int		*arg_lengths;	/* lengths of arguments */
Boolean		argv_needs_more;/* if argv is temporarily shorter than argc */

/*
 *	Note:  this is used separately by read_defaults_file.
 */

static void
arg_put(const char *p, unsigned len)
{
	unsigned args_pos_int;

	if (args_pos + len >= args_end) {
	    args_pos_int = args_pos - args;
	    do
		args_max += INCR;
	    while (args_pos_int + len >= args_max);
	    args = xrealloc(args, args_max);
	    args_pos = args + args_pos_int;
	    args_end = args + args_max;
	}
	memcpy(args_pos, p, len);
	args_pos += len;
}

static void
arg_close(int arg_begin_len)
{
	arg_put("", 1);
	if (argc >= argc_max) {
	    arg_lengths = xrealloc(arg_lengths,
	      (argc_max += ARGC_INCR) * sizeof(int));
	    argv_needs_more = True;
	}
	arg_lengths[argc] = (args_pos - args) - arg_begin_len;
	++argc;
}

const char *
arg_lookup_var(Boolean skipping)
{
	int		len_save;
	char		*str_end;
	struct var	*vp;

	++lp;
	len_save = args_pos - args;
	for (;;) {
	    if ((*lp >= 'A' && *lp <= 'Z') || (*lp >= 'a' && *lp <= 'z')
	      || (*lp >= '0' && *lp <= '9') || *lp == '_') {
		const char *p0	= lp;

		do
		    ++lp;
		while ((*lp >= 'A' && *lp <= 'Z') || (*lp >= 'a' && *lp <= 'z')
		  || (*lp >= '0' && *lp <= '9') || *lp == '_');
		arg_put(p0, lp - p0);
	    }
	    else if (*lp == '\\' && lp[1] == '\0')
		getline();
	    else
		break;
	}
	str_end = args_pos;

	args_pos = args + len_save;
	vp = find_var(args_pos, str_end - args_pos);

	if (!skipping) {
	    if (str_end - args_pos == trace_var_len
	      && memcmp(args_pos, trace_var, trace_var_len) == 0)
		printf("Variable %s used at %s:%d\n", trace_var,
		  filename, lineno);

	    vp->accessed = True;
	}

	return vp->value;
}

static Boolean
pre_tokenize(Boolean skipping)
{
	int	arg_begin_len;	/* position of start of current arg */
	Boolean	force_arg;	/* force us to start a new arg */
	int	i;
	char	*q;

	do {
	    if (*lp == ';')
		++lp;
	    else
		if (!getline())
		    return False;

	    argc = 0;
	    args_pos = args;
	    argv_needs_more = False;

	    arg_begin_len = 0;
	    force_arg = False;
	    lineno_begin = lineno;

	    for (;;) {
		if (*lp == '\0' || *lp == ';' || *lp == '#' || *lp == ' '
		  || *lp == '\t') {
		    if (force_arg || args_pos - args > arg_begin_len) {
			/* Close off current argument */
			arg_close(arg_begin_len);
			arg_begin_len = args_pos - args;
			force_arg = False;
		    }
		    while (*lp == ' ' || *lp == '\t') ++lp;
		    if (*lp == '\0' || *lp == '#' || *lp == ';')
			break;	/* end of command */
		}
		else if (*lp == '|' || *lp == '&' || *lp == '(' || *lp == ')'
		  || *lp == '<' || *lp == '>' || *lp == '`')
		    line_oops("Special character %c not supported.", *lp);
		else if (*lp == '\'') {
		    const char	*p0;

		    p0 = ++lp;
		    for (;;) {
			if (*lp == '\0')
			    line_oops("Unmatched ' at end of line");
			if (*lp == '\'')
			    break;
			++lp;
		    }
		    arg_put(p0, lp - p0);
		    ++lp;
		    force_arg = True;
		}
		else if (*lp == '"') {
		    ++lp;
		    for (;;) {
			if (*lp == '\0')
			    line_oops("Unmatched \" at end of line");
			if (*lp == '`')
			    line_oops("Special character ` not supported.");
			else if (*lp == '"')
			    break;
			else if (*lp == '\\') {
			    if (lp[1] == '\0')
				getline();
			    else {
				if (lp[1] == '`' || lp[1] == '"'
				  || lp[1] == '\\' || lp[1] == '$')
				    arg_put(lp + 1, 1);
				else
				    arg_put(lp, 2);
				lp += 2;
			    }
			}
			else if (*lp == '$') {
			    const char	*q;

			    q = arg_lookup_var(skipping);
			    if (q != NULL)
				arg_put(q, strlen(q));
			}
			else {
			    const char	*p0;

			    p0 = lp;
			    do
				++lp;
			    while (*lp != '\0' && *lp != '`' && *lp != '"'
			      && *lp != '\\' && *lp != '$');
			    arg_put(p0, lp - p0);
			}
		    }
		    ++lp;
		    force_arg = True;
		}
		else if (*lp == '\\') {
		    ++lp;
		    if (*lp == '\0')
			getline();
		    else {
			arg_put(lp, 1);
			++lp;
		    }
		}
		else if (*lp == '$') {
		    const char	*q;

		    q = arg_lookup_var(skipping);
		    if (q != NULL) {
			while (*q != '\0') {
			    if (*q == ' ' || *q == '\t') {
				if (args_pos - args > arg_begin_len) {
				    /* Close off current argument */
				    arg_close(arg_begin_len);
				    arg_begin_len = args_pos - args;
				}
				force_arg = False;
				++q;
			    }
			    else {
				const char *q0 = q;
				do
				    ++q;
				while (*q != '\0' && *q != ' ' && *q != '\t');
				arg_put(q0, q - q0);
			    }
			}
		    }
		}
		else {
		    const char	*p0;

		    p0 = lp;
		    do
			++lp;
		    while (*lp != '\0' && *lp != ';' && *lp != ' '
		      && *lp != '\t' && *lp != '|' && *lp != '&' && *lp != '('
		      && *lp != ')' && *lp != '<' && *lp != '>' && *lp != '`'
		      && *lp != '\'' && *lp != '"' && *lp != '\\'
		      && *lp != '$');
		    arg_put(p0, lp - p0);
		}
	    }
	} while (argc == 0);	/* repeat if null command */

	if (argv_needs_more) {
	    free(argv);
	    argv = xmalloc(argc_max * sizeof(char *));
	}
	q = args;
	for (i = 0; i < argc; ++i) {
	    argv[i] = q;
	    q += arg_lengths[i];
	}
	return True;
}


/*
 *	Store the default values from arch/$ARCH/defconfig
 */

static void
read_defaults_file(const char *name)
{
	char	*p;

	f = fopen(name, "r");
	if (f == NULL) {
	    perror(name);
	    exit(1);
	}

	filename = name;
	lineno = 0;

	while (getline()) {
	    p = line;
	    while (*p == ' ' || *p == '\t') ++p;
	    if (*p == '\0') continue;
	    if (*p == '#') {
		int	len;

		++p;
		if (*p != ' ') continue;
		++p;
		len = strlen(p) - 11;
		if (len <= 0 || memcmp(p + len, " is not set", 12) != 0)
		    continue;
		define_var(p, len, "n");
	    }
	    else {
		char	*p1;
		char	*p2;
		int	len;
		int	len2;

		if (!((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')
		  || (*p >= '0' && *p <= '9') || *p == '_'))
		    line_oops("syntax error");
		p1 = p;
		do
		    ++p1;
		while ((*p1 >= 'A' && *p1 <= 'Z') || (*p1 >= 'a' && *p1 <= 'z')
		  || (*p1 >= '0' && *p1 <= '9') || *p1 == '_');
		len = p1 - p;
		if (len == 0 || *p1 != '=')
		    line_oops("syntax error");
		++p1;
		if (*p1 == '"') {
		    ++p1;
		    p2 = p1;
		    for (;;) {
			if (*p2 == '\0')
			    line_oops("missing '\"'");
			if (*p2 == '"')
			    break;
			++p2;
		    }
		    len2 = p2 - p1;
		    ++p2;
		}
		else {
		    p2 = p1;
		    while (*p2 != '\0' && *p2 != '#' && *p2 != ' '
		      && *p2 != '\t')
			++p2;
		    len2 = p2 - p1;
		}
		while (*p2 == ' ' || *p2 == '\t') ++p2;
		if (*p2 != '\0' && *p2 != '#')
		    line_oops("syntax error");
		p1[len2] = '\0';
		define_var(p, len, p1);
	    }
	}
	fclose(f);
}


/*
 *	Store the information from the qconfig file.
 *	Read it into a linked list, merge-sort it, and then store it into
 *	an array for binary searching.
 */

struct qcf {
	const char	*name;
	int		len;
	const char	*value;
	Boolean		used;
	struct qcf	*next;
};

struct qcf	**qcf_array;
unsigned	qcf_count	= 0;	/* number of variables defined */

struct qcf *
sort_list(struct qcf *head, unsigned count)
{
	struct qcf	*p1, *p2;
	struct qcf	*newhead;
	struct qcf	**newheadp;
	unsigned	half;
	int		i;

	half = count / 2;
	if (half == 0)
	    return head;

	p1 = head;
	for (i = half - 1; i > 0; --i)
	    p1 = p1->next;
	p2 = sort_list(p1->next, count - half);
	p1->next = NULL;
	p1 = sort_list(head, half);

	/* Merge p1 and p2.  */
	newheadp = &newhead;
	for (;;) {
	    i = p1->len - p2->len;
	    if (i == 0)
		i = memcmp(p1->name, p2->name, p1->len);
	    if (i < 0) {	/* if p1 comes first */
		*newheadp = p1;
		newheadp = &p1->next;
		p1 = p1->next;
		if (p1 == NULL) {
		    *newheadp = p2;
		    break;
		}
	    }
	    else {
		*newheadp = p2;
		newheadp = &p2->next;
		p2 = p2->next;
		if (p2 == NULL) {
		    *newheadp = p1;
		    break;
		}
	    }
	}
	return newhead;
}


static void
read_qconfig_file(const char *name)
{
	struct qcf	*head	= NULL;
	struct qcf	*q;
	struct qcf	**qp;
	const char	*p;
	unsigned	len;

	f = fopen(name, "r");
	if (f == NULL) {
	    perror(name);
	    exit(1);
	}

	filename = name;
	lineno = 0;
	lp = line;
	*line = '\0';

	if (!pre_tokenize(False))
	    return;

	do {
	    if (argc != 1)
		line_oops("syntax error");
	    p = *argv;
	    while ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')
	      || (*p >= '0' && *p <= '9') || *p == '_')
		++p;
	    len = p - *argv;
	    if (*p != '=' || len == 0)
		line_oops("syntax error");
	    q = xmalloc(sizeof *q);
	    q->name = memcpy(xmalloc(len), *argv, len);
	    q->len = len;
	    q->value = xstrdup(p + 1);
	    q->used = False;
	    q->next = head;
	    head = q;
	    ++qcf_count;
	}
	while (pre_tokenize(False));
	fclose(f);

	head = sort_list(head, qcf_count);

	q = head;
	while (q != NULL) {	/* Check for duplicates */
	    struct qcf *q1;

	    q1 = q->next;
	    if (q1 == NULL) break;
	    if (q1->len == q->len && memcmp(q1->name, q->name, q->len) == 0) {
		fputs("Warning:  variable ", stdout);
		fwrite(q->name, 1, q->len, stdout);
		printf(" in %s was defined multiple times\n", path_qc_in);
		do {
		    q1 = q1->next;
		    --qcf_count;
		}
		while (q1 != NULL && q1->len == q->len
		  && memcmp(q1->name, q->name, q->len) == 0);
		q->next = q1;
	    }
	    q = q1;
	}

	qcf_array = qp = xmalloc(qcf_count * sizeof *qcf_array);
	for (q = head; q != NULL; q = q->next)
	    *qp++ = q;
}


static const char *
qcf_search(const char *name)
{
	int		len;
	int		n1, n2, n3;
	struct qcf	*q;
	int		i;

	if (qcf_count == 0)
	    return NULL;

	n1 = 0;		/* binary search qcf list */
	n2 = qcf_count;
	len = strlen(name);
	for (;;) {
	    n3 = (n1 + n2) / 2;
	    q = qcf_array[n3];
	    i = q->len - len;
	    if (i == 0) i = memcmp(q->name, name, len);
	    if (i == 0) {
		q->used = True;
		return q->value;
	    }
	    if (n2 - n1 <= 1) return NULL;
	    if (i < 0) n1 = n3;
	    else n2 = n3;
	}
}


/*
 *	Command execution.
 */

typedef	void	(*cmd_proc)(void);

struct cmd {
	int		len;
	const char	*str;
	int		min_args;
	int		max_args;
	cmd_proc	proc;
};

static	void	cmd_bool(void);
static	void	cmd_choice(void);
static	void	cmd_comment(void);
static	void	cmd_define_hex(void);
static	void	cmd_define_int(void);
static	void	cmd_define_string(void);
static	void	cmd_define_tristate(void);
static	void	cmd_dep_bool(void);
static	void	cmd_dep_mbool(void);
static	void	cmd_dep_tristate(void);
static	void	cmd_echo(void);
static	void	cmd_else(void);
static	void	cmd_fi(void);
static	void	cmd_hex(void);
static	void	cmd_if(void);
static	void	cmd_int(void);
static	void	cmd_ignore(void);
static	void	cmd_source(void);
static	void	cmd_string(void);
static	void	cmd_then(void);
static	void	cmd_tristate(void);
static	void	cmd_unset(void);

struct cmd	cmdtab[] = {	/* sorted by length (first), then alpha */
	{2,	"fi",			0,	0,	cmd_fi},
	{2,	"if",			3,	-1,	cmd_if},
	{3,	"hex",			3,	3,	cmd_hex},
	{3,	"int",			3,	5,	cmd_int},
	{4,	"bool",			2,	3,	cmd_bool},
	{4,	"echo",			0,	-1,	cmd_echo},
	{4,	"else",			0,	0,	cmd_else},
	{4,	"then",			0,	0,	cmd_then},
	{5,	"unset",		1,	-1,	cmd_unset},
	{6,	"choice",		3,	3,	cmd_choice},
	{6,	"source",		1,	1,	cmd_source},
	{6,	"string",		3,	3,	cmd_string},
	{7,	"comment",		1,	1,	cmd_comment},
	{7,	"endmenu",		0,	0,	cmd_ignore},
	{8,	"dep_bool",		2,	-1,	cmd_dep_bool},
	{8,	"tristate",		2,	3,	cmd_tristate},
	{9,	"dep_mbool",		2,	-1,	cmd_dep_mbool},
	{10,	"define_hex",		2,	2,	cmd_define_hex},
	{10,	"define_int",		2,	2,	cmd_define_int},
	{11,	"define_bool",		2,	2,	cmd_define_tristate},
	{12,	"dep_tristate",		2,	-1,	cmd_dep_tristate},
	{13,	"define_string",	2,	2,	cmd_define_string},
	{13,	"mainmenu_name",	1,	1,	cmd_ignore},
	{15,	"define_tristate",	2,	2,	cmd_define_tristate},
	{15,	"mainmenu_option",	1,	1,	cmd_ignore},
};

static struct cmd *
tokenize(Boolean skipping)
{
	struct cmd	*cmdp;
	int		n1, n2;
	int		len;

	if (!pre_tokenize(skipping))	/* if EOF */
	    return NULL;

	n1 = 0;			/* binary search command table */
	n2 = sizeof(cmdtab) / sizeof(*cmdtab);
	len = strlen(argv[0]);
	for (;;) {
	    int	n3;
	    int	i;

	    n3 = (n1 + n2) / 2;
	    cmdp = cmdtab + n3;
	    i = cmdp->len - len;
	    if (i == 0) i = strcmp(cmdp->str, argv[0]);
	    if (i == 0) break;
	    if (n2 - n1 <= 1)
		line_oops("invalid command \"%s\".", argv[0]);
	    if (i < 0) n1 = n3;
	    else n2 = n3;
	}

	if (!skipping) {
	    if (argc <= cmdp->min_args)
		line_oops("too few arguments");
	    if (argc - 1 > cmdp->max_args && cmdp->max_args >= 0)
		line_oops("too many arguments");
	}

	return cmdp;
}


static void
do_source(const char *name)
{
	struct cmd	*cmdp;

	f = fopen(name, "r");
	if (f == NULL) {
	    perror(name);
	    clean_exit();
	}

	filename = name;
	lineno = 0;
	lp = line;
	*line = '\0';
	for (;;) {
	    cmdp = tokenize(False);
	    if (cmdp == NULL)
		break;

	    cmdp->proc();
	}
	fclose(f);
}


/*
 *	Commands
 */

void
cmd_comment(void)
{
	fprintf(out_file, "*\n* %s\n*\n", argv[1]);
	fprintf(config_file, "\n#\n# %s\n#\n", argv[1]);
	fprintf(header_file, "\n/*\n * %s\n */\n", argv[1]);
}

void
cmd_echo(void)
{
	int	i;

	if (argc <= 1) return;

	for (i = 1;;) {
	    fputs(argv[i], stdout);
	    if (++i >= argc)
		break;
	    putchar(' ');
	}
	putchar('\n');
}

/*
 *	unset {variable ...}
 */

void
cmd_unset(void)
{
	int	i;

	for (i = 1; i < argc; ++i)
	    define_var(argv[i], strlen(argv[i]), "");
}

static void
define_tristate(const char *var, const char *value)
{
	switch (*value) {
	    case 'y':
		fprintf(config_file, "%s=y\n", var);
		fprintf(header_file, "#define %s 1\n", var);
		break;
	    case 'm':
		fprintf(config_file, "%s=m\n", var);
		fprintf(header_file, "#undef  %s\n", var);
		fprintf(header_file, "#define %s_MODULE 1\n", var);
		break;
	    case 'n':
		fprintf(config_file, "# %s is not set\n", var);
		fprintf(header_file, "#undef  %s\n", var);
		break;
	}

	define_var(var, strlen(var), value);
}

void
cmd_define_tristate(void)
{
	define_tristate(argv[1], argv[2]);
}

/*	bool question define */

void
cmd_bool(void)
{
	const char	*new	= "";
	const char	*def;
	const char	*ans, *ans2;
	const char	*defprompt = "";

	def = lookup_var(argv[2], strlen(argv[2]));
	if (def == NULL) {
	    def = "n";
	    new = "(NEW) ";
	}
	switch (*def) {
	    case 'y':
	    case 'm':
		defprompt = "Y/n/?";
		break;
	    case 'n':
		defprompt = "N/y/?";
		break;
	}

	ans = ans2 = qcf_search(argv[2]);
	if (ans == NULL) {
	    ans = "";
	    ans2 = def;
	}

	fprintf(out_file, "%s (%s) [%s] %s%s\n", argv[1], argv[2], defprompt,
	  new, ans);

	switch (*ans2) {
	    case 'y':
	    case 'Y':
	    case 'm':
	    case 'M':
		define_tristate(argv[2], "y");
		break;
	    case 'n':
	    case 'N':
		define_tristate(argv[2], "n");
		break;
	    default:
		line_oops("Invalid answer \"%s\" given for %s", ans2, argv[2]);
	}
}

/*	tristate question define */

void
cmd_tristate(void)
{
	const char	*new	= "";
	const char	*def;
	const char	*ans, *ans2;
	const char	*defprompt = "";

	if (!config_modules()) {
	    cmd_bool();
	    return;
	}

	def = lookup_var(argv[2], strlen(argv[2]));
	if (def == NULL) {
	    def = "n";
	    new = "(NEW) ";
	}
	switch (*def) {
	    case 'y':
		defprompt = "Y/m/n/?";
		break;
	    case 'm':
		defprompt = "M/n/y/?";
		break;
	    case 'n':
		defprompt = "N/y/m/?";
		break;
	}

	ans = ans2 = qcf_search(argv[2]);
	if (ans == NULL) {
	    ans = "";
	    ans2 = def;
	}

	fprintf(out_file, "%s (%s) [%s] %s%s\n", argv[1], argv[2], defprompt,
	  new, ans);

	switch (*ans2) {
	    case 'y':
	    case 'Y':
		define_tristate(argv[2], "y");
		break;
	    case 'm':
	    case 'M':
		define_tristate(argv[2], "m");
		break;
	    case 'n':
	    case 'N':
		define_tristate(argv[2], "n");
		break;
	    default:
		line_oops("Invalid answer \"%s\" given for %s", ans2, argv[2]);
	}
}

/*	dep_tristate question define {answer_dependencies} */

void
cmd_dep_tristate(void)
{
	Boolean		need_module	= False;
	int		i;
	const char	*new	= "";
	const char	*def;
	const char	*ans, *ans2;
	const char	*defprompt = "";

	for (i = 3; i < argc; ++i)
	    switch (*argv[i]) {
		case 'n':
		    define_tristate(argv[2], "n");
		    return;
		case 'm':
		    need_module = True;
		    break;
	    }

	if (!need_module) {
	    cmd_tristate();
	    return;
	}

	if (!config_modules()) {
	    cmd_bool();
	    return;
	}

	def = lookup_var(argv[2], strlen(argv[2]));
	if (def == NULL) {
	    def = "n";
	    new = "(NEW) ";
	}
	switch (*def) {
	    case 'y':
	    case 'm':
		defprompt = "M/n/?";
		break;
	    case 'n':
		defprompt = "N/m/?";
		break;
	}

	ans = ans2 = qcf_search(argv[2]);
	if (ans == NULL) {
	    ans = "";
	    ans2 = def;
	}

	fprintf(out_file, "%s (%s) [%s] %s%s\n", argv[1], argv[2], defprompt,
	  new, ans);

	switch (*ans2) {
	    case 'n':
	    case 'N':
		define_tristate(argv[2], "n");
		break;
	    case 'y':
	    case 'Y':
	    case 'm':
	    case 'M':
		define_tristate(argv[2], "m");
		break;
	    default:
		line_oops("Invalid answer \"%s\" given for %s", ans2, argv[2]);
	}
}

/*	dep_bool question define {answer_dependencies} */

void
cmd_dep_bool(void)
{
	int		i;

	for (i = 3; i < argc; ++i)
	    if (*argv[i] == 'n' || *argv[i] == 'm') {
		define_tristate(argv[2], "n");
		return;
	    }

	cmd_bool();
}

/*	dep_mbool question define {answer_dependencies} */

void
cmd_dep_mbool(void)
{
	int		i;

	for (i = 3; i < argc; ++i)
	    if (*argv[i] == 'n') {
		define_tristate(argv[2], "n");
		return;
	    }
	    else if (*argv[i] == 'm') {
		/* change the default */
		define_var(argv[2], strlen(argv[2]), "y");
	    }

	cmd_bool();
}

static void
define_int(const char *var, const char *value)
{
	const char	*p;

	p = value;
	if (*p == '-') ++p;
	if (*p == '0' && (p[1] == 'x' || p[1] == 'X')) {
	    p+=2;
	    do {
		if ((*p < '0' || *p > '9') && (*p < 'a' || *p > 'f')
		  && (*p < 'A' && *p > 'F'))
		    line_oops("Value for %s is not an integer", var);
		++p;
	    } while (*p != '\0');
	}
	else {
	    do {
		if (*p < '0' || *p > '9')
		    line_oops("Value for %s is not an integer", var);
		++p;
	    } while (*p != '\0');
	}

	fprintf(config_file, "%s=%s\n", var, value);
	fprintf(header_file, "#define %s (%s)\n", var, value);
	define_var(var, strlen(var), value);
}

/*	define_int var value */

void
cmd_define_int(void)
{
	define_int(argv[1], argv[2]);
}

/*	int question define default [min [max]] */

void
cmd_int(void)
{
	const char	*new	= "";
	const char	*def;
	const char	*ans, *ans2;
	int		min, max;
	int		i;

	def = lookup_var(argv[2], strlen(argv[2]));
	if (def == NULL) {
	    def = argv[3];
	    new = "(NEW) ";
	}

	min = -10000000;
	max = 10000000;
	if (argc > 4) {
	    min = atoi(argv[4]);
	    if (argc > 5)
		max = atoi(argv[5]);
	}

	ans = ans2 = qcf_search(argv[2]);
	if (ans == NULL) {
	    ans = "";
	    ans2 = def;
	}

	fprintf(out_file, "%s (%s) [%s] %s%s\n", argv[1], argv[2], def,
	  new, ans);

	i = atoi(ans2);
	if (i < min || i > max)
	    line_oops("Value for %s is out of range", argv[2]);

	define_int(argv[2], ans2);
}

static void
define_hex(const char *var, const char *value)
{
	const char	*p;

	p = value;
	if (*p == '0') ++p;
	if (*p == 'x' || *p == 'X')
	    value = p;

	p = value;
	do {
	    if (!((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f')
	      || (*p >= 'A' && *p <= 'F')))
		line_oops("Value for %s is not a hexadecimal integer", var);
	    ++p;
	} while (*p != '\0');

	fprintf(config_file, "%s=%s\n", var, value);
	fprintf(header_file, "#define %s 0x%s\n", var, value);
	define_var(var, strlen(var), value);
}

/*	define_hex var value */

void
cmd_define_hex(void)
{
	define_hex(argv[1], argv[2]);
}

/*	hex question define default */

void
cmd_hex(void)
{
	const char	*new	= "";
	const char	*def;
	const char	*ans, *ans2;
	const char	*p;

	def = lookup_var(argv[2], strlen(argv[2]));
	if (def == NULL) {
	    def = argv[3];
	    new = "(NEW) ";
	}

	ans = ans2 = qcf_search(argv[2]);
	if (ans == NULL) {
	    ans = "";
	    ans2 = def;
	}

	p = def;
	if (*p == '0') ++p;
	if (*p == 'x' || *p == 'X')
	    def = p;

	fprintf(out_file, "%s (%s) [%s] %s%s\n", argv[1], argv[2], def, new,
	  ans);

	define_hex(argv[2], ans2);
}

static void
define_string(const char *var, const char *value)
{
	fprintf(config_file, "%s=\"%s\"\n", var, value);
	fprintf(header_file, "#define %s \"%s\"\n", var, value);
	define_var(var, strlen(var), value);
}

/*	define_string var value */

void
cmd_define_string(void)
{
	define_string(argv[1], argv[2]);
}

/*	string question define default */

void
cmd_string(void)
{
	const char	*new	= "";
	const char	*def;
	const char	*ans, *ans2;

	def = lookup_var(argv[2], strlen(argv[2]));
	if (def == NULL) {
	    def = argv[3];
	    new = "(NEW) ";
	}

	ans = ans2 = qcf_search(argv[2]);
	if (ans == NULL) {
	    ans = "";
	    ans2 = def;
	}

	fprintf(out_file, "%s (%s) [%s] %s%s\n", argv[1], argv[2], def, new,
	  ans);

	define_string(argv[2], ans2);
}

/*	choice question choice-list default */

void
cmd_choice(void)
{
	struct choices {
		struct choices	*next;
		const char	*keys;
		const char	*var;
	};
	struct choices	*ch_head;
	struct choices	**ch_end;
	struct choices	*chp;

	const char	*new	= "";
	const char	*def;
	struct choices	*def_rec;
	const char	*ans;
	struct choices	*ans_rec;
	char		*p;

	/* Parse choice list */

	ch_end = &ch_head;
	p = argv[2];
	for (;;) {
	    while (*p == ' ' || *p == '\t') ++p;
	    if (*p == '\0') break;
	    chp = xmalloc(sizeof *chp);
	    *ch_end = chp;
	    ch_end = &chp->next;
	    chp->keys = p;
	    while (*p != '\0' && *p != ' ' && *p != '\t') ++p;
	    if (*p == '\0')
		line_oops("Odd number of entries in choice-list");
	    *p = '\0';
	    do
		++p;
	    while (*p == ' ' || *p == '\t');
	    if (*p == '\0')
		line_oops("Odd number of entries in choice-list");
	    chp->var = p;
	    while (*p != '\0' && *p != ' ' && *p != '\t') ++p;
	    if (*p == '\0') break;
	    *p++ = '\0';
	}
	*ch_end = NULL;
	if (ch_head == NULL)
	    line_oops("Empty choice list");

	/* Determine default and answer (if any) */

	def_rec = ans_rec = NULL;
	for (chp = ch_head; chp != NULL; chp = chp->next) {
	    def = lookup_var(chp->var, strlen(chp->var));
	    if (def != NULL && *def == 'y')
		def_rec = chp;
	    ans = qcf_search(chp->var);
	    if (ans != NULL && *ans == 'y')
		ans_rec = chp;
	}

	if (def_rec != NULL)
	    def = def_rec->keys;
	else {
	    def = argv[3];
	    new = "(NEW) ";
	}

	fprintf(out_file, "%s (", argv[1]);
	for (chp = ch_head;;) {
	    fputs(chp->keys, out_file);
	    chp = chp->next;
	    if (chp == NULL)
		break;
	    fputs(", ", out_file);
	}
	fprintf(out_file, ") [%s] %s%s\n", def, new,
	  ans_rec != NULL ? ans_rec->keys : "");

	if (ans_rec == NULL) {
	    ans_rec = def_rec;
	    if (ans_rec == NULL) {
		int	ans_len	= strlen(def);

		for (chp = ch_head; chp != NULL; chp = chp->next) {
		    const char *p1;

		    for (p1 = chp->keys;;) {
			const char *p2 = strchr(p1, '/');

			if (p2 == NULL) p2 = p1 + strlen(p1);
			if (p2 - p1 >= ans_len && memcmp(def, p1, ans_len) == 0)
			  {
			    if (ans_rec != NULL)
				line_oops("Default answer given for choice is ambiguous");
			    ans_rec = chp;
			}
			p1 = p2;
			if (*p1 == '\0')
			    break;
			++p1;
		    }
		}
		if (ans_rec == NULL)
		    line_oops("Default answer given for choice does not match anything");
	    }
	}

	for (chp = ch_head; chp != NULL; chp = chp->next)
	    if (chp == ans_rec) {
		define_tristate(chp->var, "y");
		fprintf(out_file, "  defined %s\n", chp->var);
	    }
	    else
		define_tristate(chp->var, "n");
}

/*	source path */

void
cmd_source(void)
{
	FILE		*save_f;
	const char	*save_filename;
	int		save_lineno;

	if (*lp == ';')
	    line_oops("source command cannot end with ';'");

	save_f = f;
	save_filename = filename;
	save_lineno = lineno;

	do_source(filename = xstrdup(argv[1]));

	free((char *) filename);
	f = save_f;
	filename = save_filename;
	lineno = save_lineno;
	lp = line;
	*line = '\0';
}


void
cmd_then(void)
{
	line_oops("misplaced \"then\"");
}


void
cmd_else(void)
{
	line_oops("misplaced \"else\"");
}


void
cmd_fi(void)
{
	line_oops("misplaced \"fi\"");
}


Boolean
sub_condition(char ***argpp, char **arg_end)
{
	char	**argp	= *argpp;

	if (strcmp(*argp, "!") == 0) {
	    if ((*argpp = argp + 1) <= arg_end)
		return !sub_condition(argpp, arg_end);
	}
	else if ((*argpp = argp + 3) <= arg_end) {
	    if (strcmp(argp[1], "=") == 0)
		return strcmp(argp[0], argp[2]) == 0;
	    else if (strcmp(argp[1], "!=") == 0)
		return strcmp(argp[0], argp[2]) != 0;
	}

	line_oops("conditional syntax error");
}


Boolean
and_list(char ***argpp, char **arg_end)
{
	char	**argp	= *argpp;
	Boolean	value;

	value = sub_condition(&argp, arg_end);
	while (argp < arg_end && strcmp(*argp, "-a") == 0) {
	    ++argp;
	    value &= sub_condition(&argp, arg_end);
	}
	*argpp = argp;
	return value;
}


static struct cmd *
if_skip(void)
{
	struct cmd	*cmdp;
	int		level	= 0;

	for (;;) {
	    cmdp = tokenize(True);
	    if (cmdp == NULL)
		return cmdp;
	    if (cmdp->proc == cmd_if)
		++level;
	    else if (cmdp->proc == cmd_else) {
		if (level == 0)
		    return cmdp;
	    }
	    else if (cmdp->proc == cmd_fi) {
		if (level == 0)
		    return cmdp;
		else
		    --level;
	    }
	}
}


void
cmd_if(void)
{
	Boolean		value;
	char		**argp;
	char		**arg_end;
	struct cmd	*cmdp;

	argp = argv + 1;
	if (strcmp(*argp, "[") != 0)
	    line_oops("conditional expression must begin with \"[\"");
	arg_end = argv + (argc - 1);
	if (strcmp(*arg_end, "]") != 0)
	    line_oops("conditional expression must end with \"]\"");

	++argp;
	value = and_list(&argp, arg_end);
	while (argp < arg_end) {
	    if (strcmp(*argp, "-o") != 0)
		line_oops("conditional syntax error");
	    ++argp;
	    value |= and_list(&argp, arg_end);
	}

	cmdp = tokenize(False);
	if (cmdp == NULL || cmdp->proc != cmd_then)
	    line_oops("\"then\" expected");

	if (value) {
	    for (;;) {
		cmdp = tokenize(False);
		if (cmdp == NULL || cmdp->proc == cmd_fi)
		    break;
		if (cmdp->proc == cmd_else) {
		    cmdp = if_skip();
		    if (cmdp != NULL && cmdp->proc == cmd_else)
			cmd_else();	/* misplaced */
		    break;
		}
		cmdp->proc();
	    }
	}
	else {
	    cmdp = if_skip();
	    if (cmdp != NULL && cmdp->proc == cmd_else) {
		for (;;) {
		    cmdp = tokenize(False);
		    if (cmdp == NULL || cmdp->proc == cmd_fi)
			break;
		    cmdp->proc();
		}
	    }
	}

	if (cmdp == NULL)
	    line_oops("File ended before \"if\" was complete");
}


void
cmd_ignore(void)
{
}


/*
 *	File commands.
 */

static void
open_writable_file(const char *path, const char *tmp_repl,
  const char **tmp_path_out, FILE **file_out)
{
	FILE		*file;
	struct stat	statbuf;

	if (path[0] == '-' && path[1] == '\0')
	    file = stdout;
	else {
	    const char *path2;

	    /*
	     * If path is /dev/null, we don't want to write a temporary regular
	     * file and then move it into place!
	     */

	    if (stat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
		path2 = path;
	    else {
		const char	*p	= strrchr(path, '/');

		if (p == NULL)
		    *tmp_path_out = path2 = tmp_repl;
		else {
		    char *tmp_path;
		    int	len1	= p - path + 1;
		    int	len2	= strlen(tmp_repl) + 1;

		    *tmp_path_out = path2 = tmp_path = xmalloc(len1 + len2);
		    memcpy(tmp_path, path, len1);
		    memcpy(tmp_path + len1, tmp_repl, len2);
		}
	    }
	    file = fopen(path2, "w");
	    if (file == NULL) {
		perror(path2);
		clean_exit();
	    }
	}
	*file_out = file;
}

static const char *
oldname(const char *path)
{
	const char	*p;
	int		len;
	char		*q;

	p = strrchr(path, '/');
	if (p == NULL) p = path;

	p = strrchr(p, '.');
	if (p == NULL) len = strlen(path);
	else len = p - path;

	q = xmalloc(len + 5);
	memcpy(q, path, len);
	memcpy(q + len, ".old", 5);

	return q;
}

static Boolean
file_exists(const char *path)
{
	struct stat	buf;

	return stat(path, &buf) == 0;
}

static void
move(const char *path1, const char *path2)
{
	if (link(path1, path2) != 0 || unlink(path1) != 0) {
	    fprintf(stderr, "%s --> ", path1);
	    perror(path2);
	}
}


/*
 *	Main program.
 */

int
main(int main_argc, char **main_argv)
{
	char		**argp;
	struct qcf	*q;
	struct var	*var_config_modversions;

	argp = main_argv;
	while (++argp < main_argv + main_argc && (*argp)[0] == '-') {
	    const struct option *opt_ptr;
	    const struct option *opt;
	    char *arg = *argp + 1;

	    if (*arg == '\0') --arg;	/* this will flag an error later */
	    if (*arg != '-') {		/* if short argument */
		opt = options;
		for (;;) {
		    if (*arg == opt->shortname)
			break;
		    if (++opt >= options + NUMBER(options))
			opt_oops("invalid option -- %c", *arg);
		}
		if (opt->addr != NULL) {
		    ++arg;
		    if (*arg == '\0') {
			if (++argp >= main_argv + main_argc)
			    opt_oops("option requires an argument -- %c",
			      arg[-1]);
			arg = *argp;
		    }
		}
		else {
		    if (arg[1] != '\0')
			opt_oops("invalid number of bytes in option `%s'",
			  arg - 1);
		}
	    }
	    else {			/* long argument */
		int	len;
		char	*arg1;

		++arg;
		len = strlen(arg);
		arg1 = memchr(arg, '=', len);
		if (arg1 != NULL) {
		    len = arg1 - arg;
		    ++arg1;
		}
		opt = NULL;
		for (opt_ptr = options; opt_ptr < options + NUMBER(options);
		  ++opt_ptr)
		    if (memcmp(arg, opt_ptr->longname, len) == 0) {
			if (opt != NULL)
			    opt_oops("option `%s' is ambiguous.", arg - 2);
			opt = opt_ptr;
		    }
		if (opt == NULL)
		    opt_oops("unrecognized option `%s'", arg - 2);
		if (opt->addr != NULL) {
		    if (arg1 == NULL) {
			if (++argp >= main_argv + main_argc)
			    opt_oops("option `--%s' requires an argument.",
			      opt->longname);
			arg1 = *argp;
		    }
		}
		else {
		    if (arg1 != NULL)
			opt_oops("option `--%s' doesn't allow an argument.",
			  opt->longname);
		}
		arg = arg1;
	    }		/* end long argument */

	    if (opt->addr != NULL)
		*((char **) opt->addr) = arg;

	    switch (opt->shortname) {
		case 'v':
		    puts("qconfig " VERSION);
		    return 0;
		case '-':	/* --help */
		    puts("\
Usage: qconfig [options]\n\
Create configuration files for Linux kernel.\n\
\n\
  -i PATH, --input=PATH	Use PATH as the input file instead of qconfig.in\n\
  -o PATH, --output=PATH Send pseudo-output file to PATH instead of qconfig.out\n\
  -c PATH, --config=PATH Create a file PATH instead of .config\n\
  -h PATH, --headers=PATH Use PATH instead of include/linux/autoconf.h\n\
  -d PATH, --defaults=PATH Get default values from PATH instead of\n\
			arch/$ARCH/defconfig\n\
  -s PATH, --script=PATH  Use PATH as the initial configure script instead of\n\
			arch/$ARCH/config.in\n\
  -a ARCH, --arch=ARCH	Use ARCH as the architecture\n\
  -t VAR, --trace=VAR	Trace variable VAR\n\
  -n, --nominal		Same as -c /dev/null -h /dev/null -o /dev/null\n\
  -v, --version		Print qconfig version number and exit\n\
  --help		Print this help message and exit\n");
		    return 0;
		case 'n':	/* --nominal */
		    path_dot_cfg = path_autoconf = path_qc_out = "/dev/null";
		    break;
	    }
	}

	if (argp != main_argv + main_argc)
	    opt_oops("Invalid option `%s'", *argp);

	if (arch == NULL) {
	    arch = getenv("ARCH");
	    if (arch == NULL)
		arch = ARCH;
	}

	if (trace_var != NULL)
	    trace_var_len = strlen(trace_var);

	if (path_defaults == NULL) {
	    path_defaults = xmalloc(16 + strlen(arch));
	    sprintf(path_defaults, "arch/%s/defconfig", arch);
	}

	if (path_config_in == NULL) {
	    path_config_in = xmalloc(16 + strlen(arch));
	    sprintf(path_config_in, "arch/%s/config.in", arch);
	}

	line = xmalloc(line_max = INCR);

	args = xmalloc(args_max = INCR);
	args_end = args + args_max;
	arg_lengths = xmalloc((argc_max = ARGC_INCR) * sizeof (int));
	argv = xmalloc(argc_max * sizeof(char *));

	var_config_modules = find_var("CONFIG_MODULES", 14);
	var_config_modules->value = xstrdup("");
	define_var("ARCH", 4, arch);
	read_defaults_file(path_defaults);

	read_qconfig_file(path_qc_in);

	open_writable_file(path_qc_out, ".tmpout", &path_tmp_out, &out_file);
	fprintf(out_file, "#\n# Using defaults found in %s\n#\n",
	  path_defaults);

	open_writable_file(path_dot_cfg, ".tmpconfig", &path_tmp_config,
	  &config_file);
	fputs("#\n# Automatically generated make config: don't edit\n#\n",
	  config_file);

	open_writable_file(path_autoconf, ".tmpconfig.h", &path_tmp_header,
	  &header_file);
	fputs("/*\n * Automatically generated C config: don't edit\n */\n",
	  header_file);
	fputs("#define AUTOCONF_INCLUDED 1\n", header_file);

	do_source(path_config_in);

	if (path_tmp_out != NULL) {
	    fclose(out_file);
	    if (file_exists(path_qc_out)) {
		const char *oldpath = oldname(path_qc_out);

		unlink(oldpath);
		move(path_qc_out, oldpath);
	    }
	    move(path_tmp_out, path_qc_out);
	}

	if (path_tmp_config != NULL) {
	    fclose(config_file);
	    if (file_exists(path_dot_cfg)) {
		const char *oldpath = oldname(path_dot_cfg);

		unlink(oldpath);
		move(path_dot_cfg, oldpath);
	    }
	    move(path_tmp_config, path_dot_cfg);
	}

	if (path_tmp_header != NULL) {
	    fclose(header_file);
	    unlink(path_autoconf);
	    move(path_tmp_header, path_autoconf);
	}

	if (qcf_array != NULL)
	    for (q = *qcf_array; q != NULL; q = q->next)
		if (!q->used) {
		    fputs("Note:  variable ", stdout);
		    fwrite(q->name, 1, q->len, stdout);
		    printf(" in %s was not used.\n", path_qc_in);
		}

	puts(
	  "\n*** Check the top-level Makefile for additional configuration.");

	var_config_modversions = find_var("CONFIG_MODVERSIONS", 18);
	if (!file_exists(".hdepend")
	  || (var_config_modversions != NULL
	  && *var_config_modversions->value == 'y'))
	    puts("*** Next, you must run 'make dep'.\n");
	else
	    puts("*** Next, you may run 'make zImage', 'make zdisk', or 'make zlilo'.\n");

	return 0;
}
