/* Copyright (c) 1994-2000 Begemot Computer Associates.
 * Copyright (c) 2000-2002 by Hartmut Brandt
 *	All rights reserved.
 *
 * Redistribution of this software and documentation and use in source and
 * binary forms, with or without modification, are permitted provided that
 * the following conditions are met:
 *
 * 1. Redistributions of source code or documentation must retain the above
 *    copyright notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *
 * This product includes software and documentation developed by the
 * Hartmut Brandt.
 *
 * 4. The name of the author or its contributors may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE AUTHOR FOKUS
 * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * THE AUTHOR OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: main.c 502 2002-09-04 13:31:20Z hbb $
 *
 * Author: Harti Brandt <brandt@fokus.gmd.de>
 *
 * Emulator start and configuration stuff.
 */

#include "proc.h"
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <begemot.h>

RCSID("$Id: main.c 502 2002-09-04 13:31:20Z hbb $")

proc_t	proc;
char	verbose[128];
int	isdaemon;
char	*pidfile = "/var/run/p11.pid";

static int daemonized;

#define MAXARGS 100
#define MAXSUBST 100
#define MAXNAME 33
#define MAXIF 100
#define MAXINCDIR 100

typedef struct conffile {
	struct conffile *next;
	char	*name;		/* name of this file */
	int	line;		/* line number of this file */
	FILE	*fp;		/* file pointer */
} conffile_t;

static char	*defname = P11CONF;
static conffile_t *conffile;

static char	*incdirs[MAXINCDIR] = {
	".",
	LIBPATH,
};
static u_int	nincdirs = 2;


jmp_buf	trap;
int	fpp = 0;
u_short	startloc = 0173000;
int	nomon;		/* don't have a monitor */

glob_t	globopts = {
	NULL,
	NULL,
	50,
};

static opt_t glob_opts[] = {
	{ "libpath",  		OptString,	offsetof(glob_t, libpath) },
	{ "iodir",		OptString,	offsetof(glob_t, iodir) },
	{ "clock_rate",		OptUInt,	offsetof(glob_t, clock_rate)},
	{ NULL,			OptInt,		0}
};

typedef struct name {
	char	*name;
	char	*val;
	struct name *next;
} name_t;

static name_t	*names;


extern ioops_t lp_ops, rl_ops, kl_ops, mr_ops, rp_ops, rk_ops;
extern ioops_t tm_ops, qna_ops, proc_ops, memsys_ops, toy_ops;
extern opt_t toy_opts[], proc_opts[];

static ioconf_t ioconf[] = {
	{ "KDJ11A", &proc_ops, 0, proc_opts },	/* must be 0 */
	{ "mem", &memsys_ops, 0, NULL },	/* must be 1 */
	{ "rl",	&rl_ops, 0, NULL },
	{ "kl",	&kl_ops, 0, NULL },
	{ "mr", &mr_ops, 0, NULL },
	{ "lp", &lp_ops, 0, NULL },
	{ "rp", &rp_ops, 0, NULL },
	{ "tm", &tm_ops, 0, NULL },
	{ "qna", &qna_ops, 0, NULL },
	{ "rk", &rk_ops, 0, NULL },
	{ "toy", &toy_ops, 0, toy_opts },
};

#define NCONF (sizeof(ioconf)/sizeof(ioconf[0]))

static char usgtxt[] =
"Begemot PDP11 emulator Version %s\n"
"Copyright (c) 1994-2000 Begemot Computer Associates. All rights reserved.\n"
"\n"
"Usage: p11 [options] [file]\n"
"\n"
"'file' is a file to be loaded and can be an un*x v7 a.out file or\n"
"a special formatted hexdump.\n"
"\n"
"options:\n"
"	-h		print this text\n"
"	-b		batch mode, start execution and exit on halt\n"
"	-c file		use file as configuration file\n"
"	-d		daemonize (includes -b)\n"
"	-f 		toggle floating point unit\n"
"	-p file		pid file for daemon\n"
"	-s loc		set initial pc to loc\n"
"	-v string	switch on debug options given in string\n"
"	-D var[=val]	define variable var\n"
"	-I path		set include path for configuration files\n"
"	-S opt=val	set option\n"
"\n"
"Type '?' on the '@' prompt to see a list of commands.\n"
"\n"
"Please report bugs and enhancements to brandt@fokus.gmd.de\n";

static void	Setup(void);
static void	LoadFile(char *);
static void	catch_sigs(void);
static void	catch_sigchild(void);
static void	onpipe(int);
static void	add_incdir(char *);
static void	add_define(char *);
static void	add_option(char *);
static void	set_option(char *name, char *val);
static void	xverb(char, char *, ...) FORMAT(printf, 2, 3);
static iodev_t	*create_device(ioconf_t *conf, int argc, char **argv);
static void	daemonize1(void);
static void	daemonize2(void);
static FILE	*path_open(const char *fname, char **fn, const char *path);

int
main(int argc, char *argv[])
{
	int	opt;
	u_char	*p;
	extern int optind;
	extern char *optarg;

	set_argv0(argv[0]);
	while((opt = getopt(argc, argv, "S:hbfs:c:v:D:I:d")) != EOF) {
		switch(opt) {

		  case 'h':
			fprintf(stderr, usgtxt, VERSION);
			return 0;

		  case 'c':
			defname = optarg;
			break;

		  case 'd':
			nomon = isdaemon = 1;
			break;

		  case 'v':
			for(p = (u_char *)optarg; p && *p; p++)
				if(*p < sizeof(verbose))
					verbose[(int)*p]++;
			break;

		  case 'I':
			add_incdir(optarg);
			break;

		  case 'D':
			add_define(optarg);
			break;

		  case 's':
			if(optarg)
				 sscanf(optarg, "%hi", &startloc);
			break;

		  case 'f':
			fpp = 1;
			break;

		  case 'b':
			nomon = 1;
			break;

		  case 'S':
			add_option(optarg);
			break;
		}
	}
	argc -= optind;
	argv += optind;

	catch_sigs();

	if (isdaemon)
		daemonize1();

	Setup();

	while(argc-- > 0)
		LoadFile(*argv++);

	catch_sigchild();

	init_timer();

	if (isdaemon)
		daemonize2();

	start_timer();
	interp();
}

static void
rmpidfile(void)
{
	if (remove(pidfile) == -1)
		syslog(LOG_ERR, "%s: %m", pidfile);
}

static void
onterm(int s)
{
	s = s;

	proc_pwrfail(1);
}

static void
daemonize1(void)
{
	FILE *pfile;
	pid_t pid;
	u_long num;
	int fd;

	/*
	 * Check whether we are already running.
	 */
	if ((pfile = fopen(pidfile, "r")) != NULL) {
		if (fscanf(pfile, "%lu", &num) == 1) {
			if (kill((pid_t)num, 0) != -1) {
				fprintf(stderr, "p11 already running (%lu)\n",
				    num);
				exit(1);
			}
			if (errno == ESRCH) {
				fprintf(stderr, "stale pid file - removing\n");
			} else {
				fprintf(stderr, "checking for running "
				    "p11 returns %s for pid %lu\n",
				    strerror(errno), num);
			}
		}
		fclose(pfile);
		if (remove(pidfile) == -1) {
			perror(pidfile);
			exit(1);
		}
	}

	/*
	 * Fork if we are going to be a daemon
	 */
	if ((pid = fork()) == -1) {
		perror("cannot fork");
		exit(1);
	}
	if (pid != 0) {
		/* parent */
		_exit(0);
	}

	catch_signal(SIGTERM, onterm);

	/*
	 * Write the new pid file
	 */
	if ((pfile = fopen(pidfile, "w")) == NULL) {
		syslog(LOG_ERR|LOG_CONS, "%s: %m", pidfile);
		exit(1);
	}
	fprintf(pfile, "%lu", (u_long)getpid());
	fclose(pfile);

	if (atexit(rmpidfile) == -1)
		syslog(LOG_WARNING|LOG_CONS, "atexit: %m");

	/*
	 * Get a new session
	 */
	if (setsid() == -1) {
		syslog(LOG_ERR|LOG_CONS, "setsid: %m");
		exit(1);
	}

	if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
		(void)dup2(fd, STDIN_FILENO);
		(void)dup2(fd, STDOUT_FILENO);
		(void)dup2(fd, STDERR_FILENO);
		if (fd > 2)
			(void)close(fd);
	}

	daemonized = 1;
}

static void
daemonize2(void)
{
	(void)chdir("/");
}

static char	*substitutions(char *);
static void	parse_statement(char *);

static void	parse_print(char *);
static void	parse_device(char *);
static void	parse_define(char *);
static void	parse_set(char *);
static void 	parse_ctrl(char *);
static void 	parse_end(char *);
static void	parse_if(char *);
static void	parse_elif(char *);
static void	parse_endif(char *);
static void	parse_else(char *);
static void	parse_include(char *);
static void	parse_eval(char *);

static long	expr(char *);

static struct {
	char	*name;
	void	(*func)(char *);
	int	iff;
} func_tab[] = {
	{ "dev",	parse_device,	0 },
	{ "define",	parse_define,	0 },
	{ "set",	parse_set,	0 },
	{ "ctrl",	parse_ctrl,	0 },
	{ "end",	parse_end,	0 },
	{ "if",		parse_if,	1 },
	{ "elif",	parse_elif,	1 },
	{ "else",	parse_else,	1 },
	{ "endif",	parse_endif,	1 },
	{ "include",	parse_include,	0 },
	{ "print",	parse_print,	0 },
	{ "eval",	parse_eval,	0 },
	{ NULL,		NULL,		0 }
};

typedef enum {
	TR_FALSE,
	TR_TRUE,
	TR_NEVER
} truth_t;

static truth_t	ifstack[MAXIF];
static int	ifstack_ptr;

static iodev_t *act_dev = NULL;	/* created controller */

static char *conf_nextline(void);
static void conf_open(const char *);
static void do_define(char *, char *);
static void check_name(char *);

static void
Setup(void)
{
	char *cp, *line;

	if (globopts.libpath == NULL)
		set_option("libpath", LIBPATH);
	if (globopts.iodir == NULL)
		set_option("iodir", IODIR);

	do_define("SYSTEM", SYSTEMS);
	do_define("PROC", PROCS);

	catch_io_sigs();

	xverb('c', "reading configuration from %s ...\n", defname);
	memset((caddr_t)&proc, 0, sizeof(proc));
	proc.physmem = PHYSMEM;
	proc.bp = 0177777;
#ifndef FPP
	proc.fpd = !fpp;
#else
	proc.fpd = fpp;
#endif

	(void)setfields(" \t\n");

	conf_open(defname);

	ifstack[0] = TR_TRUE;
	while((line = conf_nextline()) != NULL) {
		line[strlen(line)-1] = 0;
		conffile->line++;
		xverb('i', "%d: %s", conffile->line, line);

		cp = line;
		strtrimsp(&cp, 2);
		if(*cp != 0 && *cp != '#')
			parse_statement(cp);
		free(line);
	}
	if(act_dev != NULL)
		conf_panic("EOF in controller declaration");

	if(ifstack_ptr != 0)
		conf_panic("EOF in if construct");

	/*
	 * Ensure that memory is proc.devices[1] and proc
	 * proc.devices[0]
 	 */
	if(proc.devices == NULL || proc.devices->conf != &ioconf[0])
		conf_panic("No processor specified in configuration file or processor\n"
			"is not the last devices. Ensure that the processor is added\n"
			"last to the configuration in the configuration file. The last two\n"
			"(noncomment) lines should read:\n"
			"ctrl KDJ11A\n"
			"end\n"
			"Options can be set between those two lines.\n");
	if(proc.devices->next == NULL || proc.devices->next->conf != &ioconf[1])
		conf_panic("No memory specified in configuration file or memory is\n"
			"not located immediately before the processor. Ensure that\n"
			"just before specifying the processor you have the lines:\n"
			"ctrl mem\n"
			"end\n"
			"in your configuration file.\n");

	proc.proc = proc.devices;

#ifndef FPP
	if(!proc.fpd)
		panic("no fpp but fpp enabled\n");
#endif
}

static void
add_incdir(char *arg)
{
	if(nincdirs == MAXINCDIR)
		panic("too many -I directories (max is %d)", MAXINCDIR);

	incdirs[nincdirs++] = strsave(arg);
}

static void
add_define(char *arg)
{
	char *val;

	if((val = strchr(arg, '=')) == NULL)
		val = "";
	else
		*val++ = '\0';

	check_name(arg);
	do_define(arg, val);
}

static void
add_option(char *arg)
{
	char *val;

	if((val = strchr(arg, '=')) == NULL)
		val = "";
	else
		*val++ = '\0';

	check_name(arg);
	set_option(arg, val);
}

/*
 * If the filename contains at least one slash, we assume, that this
 * is the complete pathname to the script and try to open it. If not,
 * we loop through the library path and try each element, until one succeedes.
 */
static void
conf_open(const char *fname)
{
	FILE *fp;
	conffile_t *cf;
	char *fn;
	u_int i;

	fp = NULL;
	for (i = 0; i < nincdirs; i++)
		if ((fp = path_open(fname, &fn, incdirs[i])) != NULL)
			break;
	if (fp == NULL)
		conf_panic("%s: %s", fname, strerror(errno));

	cf = xalloc(sizeof(*cf));
	cf->next = conffile;
	conffile = cf;

	cf->name = fn;
	cf->line = 0;
	cf->fp = fp;
}

static char *
conf_nextline()
{
	char *line;
	conffile_t *cf;

	cf = conffile;
	while((line = readline(cf->fp)) == NULL) {
		if(feof(cf->fp)) {
			fclose(cf->fp);
			free(cf->name);
			conffile = cf->next;
			free(cf);
			if((cf = conffile) == NULL)
				return NULL;
		} else if(ferror(cf->fp)) {
			conf_panic("read: %s", strerror(errno));
		}
	}
	return line;
}

/*
 * Parse one statement. If it is a ctrl, return a IODev structure.
 */
static void
parse_statement(char *line)
{
	char *argv[2];
	int argc;

	argc = getmfields(line, argv, 2);
	if(argc == 0)
		bug("ups");

	for(argc = 0; func_tab[argc].name; argc++)
		if(strcmp(argv[0], func_tab[argc].name) == 0)
			break;

	if(func_tab[argc].name == NULL) {
		if(ifstack[ifstack_ptr] == TR_TRUE)
			conf_panic("unknown keyword '%s'", argv[0]);
	} else {
		if(func_tab[argc].iff || ifstack[ifstack_ptr] == TR_TRUE)
			(*func_tab[argc].func)(argv[1]);
	}
}

static void
check_name(char *cp)
{
	if(*cp == 0)
		conf_panic("null name");
	if(!isascii(*cp) || (!isalpha((int)*cp) && *cp != '_'))
bad:		conf_panic("bad character in name '%c'", *cp);
	cp++;
	while(*cp) {
		if(!isascii(*cp) || (!isalnum((int)*cp) && *cp != '_'))
			goto bad;
		cp++;
	}
}


static name_t *
find_name(char *line)
{
	name_t *np;

	for(np = names; np != NULL; np = np->next)
		if(strcmp(np->name, line) == 0)
			return np;
	return NULL;
}

static void
parse_include(char *line)
{
	int argc;
	char *argv[2];

	argc = getmfields(line, argv, 2);
	if(argc == 0)
		conf_panic("need file name after 'include'");

	if(argc > 1 && argv[1][0] != '#')
		conf_warning("junk ignored after 'include'");

	conf_open(argv[0]);
}

static void
parse_if(char *line)
{
	char *n;

	if(ifstack_ptr == MAXIF - 1)
		conf_panic("too many if's");

	if(ifstack_ptr != 0 && ifstack[ifstack_ptr] != TR_TRUE) {
		ifstack[++ifstack_ptr] = TR_NEVER;
		return;
	}

	n = substitutions(line);
	ifstack[++ifstack_ptr] = expr(n) ? TR_TRUE : TR_FALSE;
	free(n);
}

static void
parse_elif(char *line)
{
	char *n;

	if(ifstack_ptr == 0) {
		conf_warning("dangling else");
		parse_if(line);
		return;
	}

	if(ifstack[ifstack_ptr] == TR_TRUE)
		ifstack[ifstack_ptr] = TR_NEVER;
	else if(ifstack[ifstack_ptr] == TR_NEVER)
		return;

	/* TR_FALSE */

	n = substitutions(line);
	ifstack[ifstack_ptr] = expr(n) ? TR_TRUE : TR_FALSE;
	free(n);
}

static void
parse_endif(char *line)
{
	if(line != NULL)
		conf_warning("junk after 'endif' ignored");
	if(ifstack_ptr == 0) {
		conf_warning("too many endif's");
		return;
	}
	ifstack_ptr--;
}

static void
parse_else(char *line)
{
	if(line != NULL)
		conf_warning("junk after 'else' ignored");
	if(ifstack_ptr == 0) {
		conf_warning("dangling else");
		return;
	}
	if(ifstack[ifstack_ptr] == TR_TRUE)
		ifstack[ifstack_ptr] = TR_FALSE;
	else if(ifstack[ifstack_ptr] == TR_FALSE)
		ifstack[ifstack_ptr] = TR_TRUE;
}


static void
parse_define(char *line)
{
	int argc;
	char *argv[2];

	argc = getmfields(line, argv, 2);
	if(argc == 0)
		conf_panic("need name after 'define'");

	check_name(argv[0]);
	do_define(argv[0], argv[1]);
}

static void
parse_eval(char *line)
{
	int argc;
	char *argv[2];
	char buf[100], *n;

	argc = getmfields(line, argv, 2);
	if(argc == 0)
		conf_panic("need name after 'eval'");

	check_name(argv[0]);
	n = substitutions(argv[1]);
	sprintf(buf, "%ld", expr(n));
	do_define(argv[0], buf);
	free(n);
}

static void
do_define(char *name, char *val)
{
	name_t *np;

	/*
	 * Name ok, define
	 */
	if((np = find_name(name))== NULL) {
		np = xalloc(sizeof(name_t));
		np->name = strsave(name);
		np->val = substitutions(val);
		np->next = names;
		names = np;
		xverb('c', "defining '%s' to '%s'\n", name, np->val);
	} else {
		free(np->val);
		np->val = substitutions(val);
		xverb('c', "redefining '%s' to '%s'\n", name, np->val);
	}
}

static void
parse_set(char *line)
{
	char	*value;
	int argc;
	char *argv[2];

	argc = getmfields(line, argv, 2);
	if(argc == 0)
		conf_panic("need option name after 'set'");

	value = substitutions(argv[1]);
	set_option(argv[0], value);
	free(value);
}

static void
set_option(char *name, char *val)
{
	int ival;
	char *end, buf[100];
	u_int uval;
	opt_t *opts;
	void *data;
	char **sptr;

	if(act_dev == NULL) {
		opts = glob_opts;
		data = (void *)&globopts;
	} else {
		if((opts = act_dev->conf->opts) == NULL) {
			conf_warning("no options for %s", ctrlname(act_dev, 0));
			return;
		}
		data = act_dev->data;
	}

	while(opts->name != NULL) {
		if(strcmp(opts->name, name) == 0)
			break;
		opts++;
	}

	if(opts->name == NULL) {
		conf_warning("unknown option name: %s", name);
		return;
	}

	switch(opts->type) {

	  case OptString:
		sptr = (char **)((char *)data + opts->off);
 		if(*sptr != NULL)
			free(*sptr);
		*sptr = strsave(val);
		xverb('c', "setting option '%s' to '%s'\n", name, val);
		do_define(name, val);
		break;

	  case OptInt:
		ival = (int)strtol(val, &end, 0);
		if(*end != '\0')
			conf_panic("bad integer '%s'", val);
		*(int *)((char *)data + opts->off) = ival;
		xverb('c', "setting option '%s' to '%d'\n", name, ival);
		sprintf(buf, "%d", ival);
		do_define(name, buf);
		break;

	  case OptUInt:
		uval = (u_int)strtoul(val, &end, 0);
		if(*end != '\0')
			conf_panic("bad unsigned integer '%s'", val);
		*(u_int *)((char *)data + opts->off) = uval;
		xverb('c', "setting option '%s' to '%u'\n", name, uval);
		sprintf(buf, "%u", uval);
		do_define(name, buf);
		break;

	  case OptFlag:
		if(strcmp(val, "0") == 0 || strcasecmp(val, "no") == 0)
			*(int *)((char *)data + opts->off) = 0;
		else if(strcmp(val, "1") == 0 || strcasecmp(val, "yes") == 0)
			*(int *)((char *)data + opts->off) = 1;
		else
			conf_panic("bad flag '%s' for '%s'", val, name);
		xverb('c', "setting option '%s' to '%s'\n", name, val);
		sprintf(buf, "%d", *(int *)((char *)data + opts->off));
		do_define(name, buf);
		break;

	  case OptFunc:
		(**(void (**)(char *))((char *)data + opts->off))(val);
		xverb('c', "setting option '%s' to '%s'\n", name, val);
		do_define(name, val);
		break;

	  default:
		panic("bad option type %d", opts->type);
	}

}

static void
parse_ctrl(char *line)
{
	int argc;
	u_int i;
	char *argv[MAXARGS];

	if(act_dev != NULL) {
		conf_warning("end statement missing");
		parse_end(NULL);
	}

	argc = getmfields(line, argv, 2);
	if(argc == 0)
		conf_panic("need controller name after 'ctrl'");

	for(i = 0 ; i < NCONF; i++)
		if(!strcmp(ioconf[i].xname, argv[0]))
			break;

	if(i == NCONF)
		conf_panic("no such ctrl \"%s\"", argv[0]);

	line = substitutions(argv[1]);
	argc = getmfields(line, argv, MAXARGS);

	act_dev = create_device(&ioconf[i], argc, argv);

	free(line);
}

static void
parse_end(char *line)
{
	if(line != NULL)
		conf_warning("junk ignored after 'end'");
	if(act_dev == NULL)
		conf_warning("dangling 'end'");

	xverb('c', "controller complete\n");
	if(act_dev->ioops->ctrl_complete != NULL)
		(*act_dev->ioops->ctrl_complete)(act_dev);
	act_dev = NULL;
}

static void
parse_print(char *line)
{
	line = substitutions(line);
	printf("%s,%d: %s\n", conffile->name, conffile->line, line);
	free(line);
}

static void
parse_device(char *line)
{
	int argc, j;
	char *argv[MAXARGS];

	if(act_dev == NULL)
		conf_panic("no controller for device");

	line = substitutions(line);
	argc = getmfields(line, argv, MAXARGS);

	if(argc == 0) {
		/* ignore lines which have only 'dev' */
		free(line);
		return;
	}

	if(verbose['c']) {
		xverb('c', "  create controller %s(", act_dev->conf->xname);
		for(j = 0; j < argc; j++)
			xverb('c', "%s%s", argv[j], (j==argc-1)?"":",");
		xverb('c', ")\n");
	}
	if(act_dev->ioops->dev_create == NULL)
		conf_panic("'%s' may not have devices", act_dev->conf->xname);

	(*act_dev->ioops->dev_create)(act_dev, argc, argv);

	free(line);
}

char *stack[MAXSUBST];
int stack_ptr;

static int
nextc(void)
{
	while(*stack[stack_ptr] == 0) {
		if(stack_ptr == 0)
			return EOF;
		stack_ptr--;
	}
	return (int)*stack[stack_ptr]++ & 0377;
}

static void
pushstr(char *str)
{
	if(stack_ptr == MAXSUBST - 1)
		conf_panic("substitution stack overflow");
	stack[++stack_ptr] = str;
}

static char *
substitutions(char *line)
{
	name_t *np;
	char *nline;
	u_int len, ptr;
	int c;
	char name[MAXNAME];
	int namelen;
	int qflag;

	if(line == NULL)
		return strsave("");

	len = 100;
	ptr = 0;
	nline = xalloc(len);

	stack_ptr = -1;
	pushstr(line);

#define STUFF_C(C)					\
	{if(ptr + 1 <= len)				\
		nline = xrealloc(nline, len += 100);	\
	 nline[ptr++] = (C);				\
	}
#define STUFF_S(S,L)					\
	{if(ptr + (L) <= len) {				\
		len += (L) + 100;			\
		nline = xrealloc(nline, len);		\
	 }						\
	 strncpy(nline + ptr, (S), (L));		\
	 ptr += (L);					\
	}

	while(1) {
		while((c = nextc()) != EOF && c != '$')
			STUFF_C(c);
		if(c == EOF)
			break;
		if((c = nextc()) == '$') {
			STUFF_C(c);
			continue;
		}
		if(c != '(')
			conf_panic("expecting '$' or '(' after '$'");
		/*
		 * Collect the name
		 */
		namelen = 0;
		while((c = nextc()) != EOF && isascii(c) && (isalnum(c) || c == '_')) {
			if(namelen == MAXNAME - 1)
				conf_panic("name too long");
			name[namelen++] = c;
		}
		name[namelen] = 0;

		/*
		 * Collect flags
		 */
		qflag = 0;
		if(c == '?') {
			qflag = 1;
			c = nextc();
		}
		if(c == EOF)
			conf_panic("EOF in substitution");
		if(!isascii(c) || c != ')')
			conf_panic("illegal char in name '0x%02x'", c);

		check_name(name);
		for(np = names; np != NULL; np = np->next)
			if(strcmp(np->name, name) == 0)
				break;
		if(qflag) {
			if(np != NULL)
				pushstr(np->val);
		} else {
			if(np == NULL)
				conf_panic("undefined name '%s'", name);
			pushstr(np->val);
		}
	}
	STUFF_C(0);
	return nline;
}

/*
 * create device with given name and operations
 */
static iodev_t *
create_device(ioconf_t *conf, int argc, char **argv)
{
	iodev_t	*dev;
	int	j;

	xverb('c', "create controller %s(", conf->xname);
	for(j = 0; j < argc; j++)
		xverb('c', "%s%s", argv[j], (j==argc-1)?"":",");
	xverb('c', ")\n");

	dev = xalloc(sizeof(iodev_t));
	memset(dev, 0, sizeof(iodev_t));

	dev->ioops = conf->ioops;
	dev->instance = conf->count++;
	dev->conf = conf;

	if(dev->ioops->ctrl_create)
		(*dev->ioops->ctrl_create)(dev, argc, argv);

	dev->next = proc.devices;
	proc.devices = dev;

	return dev;
}


void
Reset(void)
{
	iodev_t *dev;

	/*
	 * reset outstanding requests and then call each device
	 * this gives them a chance to place a new request
	 */
	proc.reqpri = 0;
	for(dev = proc.devices; dev; dev = dev->next)
		if(dev->ioops->reset)
			(*dev->ioops->reset)(dev);
}

void
Flush(void)
{
	iodev_t *dev;

	for(dev = proc.devices; dev; dev = dev->next)
		if(dev->ioops->flush)
			(*dev->ioops->flush)(dev);
}

void
conf_warning(char *fmt, ...)
{
	va_list	ap;
	conffile_t *cf;

	if (daemonized) {
		if ((cf = conffile) != NULL)
			syslog(LOG_WARNING, "Warning: %s, line %d:",
			    cf->name, cf->line);

		va_start(ap, fmt);
		vsyslog(LOG_WARNING|LOG_CONS, fmt, ap);
		va_end(ap);

		if (cf != NULL) {
			while ((cf = cf->next) != NULL)
				syslog(LOG_WARNING,
				    "  called from %s, line %d\n",
				    cf->name, cf->line);
		}
		return;
	}

	fprintf(stderr, "Warning: ");
	if((cf = conffile) != NULL)
		fprintf(stderr, "%s, line %d : ", cf->name, cf->line );

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fprintf(stderr, "\n");

	if(cf) {
		while((cf = cf->next)) {
			fprintf(stderr, "  called from %s, line %d\n", cf->name, cf->line);
		}
	}
}

void
conf_panic(char *fmt, ...)
{
	va_list	ap;
	conffile_t *cf;

	if (daemonized) {
		if ((cf = conffile) != NULL)
			syslog(LOG_ERR, "Error: %s, line %d:",
			    cf->name, cf->line);

		va_start(ap, fmt);
		vsyslog(LOG_ERR|LOG_CONS, fmt, ap);
		va_end(ap);

		if (cf != NULL) {
			while ((cf = cf->next) != NULL)
				syslog(LOG_ERR,
				    "  called from %s, line %d\n",
				    cf->name, cf->line);
		}
		exit(27);
	}

	fprintf(stderr, "Panic: ");
	if((cf = conffile))
		fprintf(stderr, "%s, line %d : ", cf->name, cf->line );

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fprintf(stderr, "\n");

	if(cf) {
		while((cf = cf->next)) {
			fprintf(stderr, "  called from %s, line %d\n", cf->name, cf->line);
		}
	}
	exit(27);
}

u_int
parse_csr(char *p, char *text)
{
	u_int csr;
	char	*end;

	csr = strtol( p, &end, 8);
	if( p == end )
		goto bad;
	if( csr <= 017760000 || csr > 017777776 )
		goto bad;
	if( csr & 1 )
		goto bad;
	return csr;
bad:
	conf_panic("%s:bad csr", text);
}

u_short
parse_vec(char *p, char *text)
{
	u_short	vec;
	char	*end;

	vec = strtol( p, &end, 8);
	if( p == end )
		goto bad;
	if( vec >= 01000 )
		goto bad;
	if( vec & 3 )
		goto bad;
	return vec;
bad:
	conf_panic("%s:bad vec", text);
}

u_short
parse_irq(char *p, char *text)
{
	u_short	irq;
	char	*end;

	irq = strtol( p, &end, 8);
	if( p == end )
		goto bad;
	if( irq < 1 || irq >  7 )
		goto bad;
	return irq;
bad:
	conf_panic("%s:bad irq", text);
}


/*
 * here we catch SIGCHLG and SIGPIPE 
 * Note: we get SIGCHLD not only when a child dies but also if
 * one of the STOP or CONT signals is given
 * so don't drop into monitor if no one died
 */
static void
onpipe(int sig UNUSED)
{
	pid_t	pid;
	int	status;
	int	got = 0;

	while((pid = waitpid((pid_t)-1, &status, WNOHANG)) > 0) {
		if(!got) {
			printf("got SIGPIPE/SIGCHLD\n");
			printf("waiting for children:\n");
			got++;
			proc.halt = 1;
		}
		printf("%4d: ", (int)pid);
		if(WIFEXITED(status)) 
			printf("exited %d\n", WEXITSTATUS(status));
		else if(WIFSIGNALED(status)) {
			printf("signaled %d", WTERMSIG(status));
			if(WCOREDUMP(status))
				printf(" core dumped");
			printf("\n");
		} else if(WIFSTOPPED(status))
			printf("stopped %d\n", WSTOPSIG(status));
		else
			printf("???\n");
	}
}

static void 
onintr(int sig UNUSED) 
{ 
	proc.halt = 1;
}

static void	
catch_sigs()
{
	catch_signal(SIGPIPE, onpipe);
	if (!isdaemon)
		catch_signal(SIGINT, onintr);
}

static void	
catch_sigchild(void)
{
	catch_signal(SIGCHLD, onpipe);
}

/*
 * V7 a.out header
 */
typedef struct aout_t {
	u_short	a_magic;	/* magic word */
	u_short	a_text;		/* text segment size */
	u_short	a_data;		/* data segment size */
	u_short	a_bss;		/* block storage section */
	u_short	a_syms;		/* symbol table size */
	u_short	a_entry;	/* program entry point */
	u_short	a_unused;	/* */
	u_short	a_stripped;	/* no relocation info */
} aout_t;

#define MAGIC1 0407	/* can load only these */

static void	
LoadFile(char *f)
{
	FILE *fp;
	u_long addr, cnt;
	u_int v;
	aout_t	a;
	u_short magic;
	struct stat statb;
	size_t size;

	if((fp = fopen(f, "r")) == 0 || fstat(fileno(fp), &statb))
		panic("%s: %s", f, strerror(errno));
	if((size_t)statb.st_size >= sizeof(aout_t)) {
		/*
		 * may be V7 a.out
		 */
		if(fread(&a, sizeof(a), 1, fp) != 1)
			panic("%s: %s", f, strerror(errno));
		if(GETW(a.a_magic) == MAGIC1) {
			size = (size_t)GETW(a.a_text) + (size_t)GETW(a.a_data);
			if(statb.st_size - sizeof(aout_t) < size)
				panic("%s: bad a.out format", f);
			addr = 0;
			size >>= 1;
			while(size--) {
				if(fread(&magic, 2, 1, fp) != 1)
					panic("%s: %s", f, strerror(errno));
				*proc.memops->addrphys(proc.mmg, addr) = magic;
				addr += 2;
			}
			size = a.a_bss;
			size >>= 1;
			while(size--) {
				*proc.memops->addrphys(proc.mmg, addr) = 0;
				addr += 2;
			}
			fclose(fp);	
			proc.reg[7] = a.a_entry & ~1;
			return;
		}
	}

	fseek(fp, 0L, 0);
	while(fscanf(fp, "%lx%lx", &addr, &cnt) == 2) {
		while(cnt-- && addr < PHYSMEM && fscanf(fp, "%x", &v) == 1) {
			u_short *p = proc.memops->addrphys(proc.mmg, addr);
			if(addr & 1)
				SETHB(*p, v);
			else
				SETLB(*p, v);
			addr++;
		}
	}
	fclose(fp);
}

/*
 * verbosity
 */
static void	
xverb(char c, char *fmt, ...)
{
	va_list	ap;

	if(!verbose[(int)c])
		return;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
}

/*
 * Expression parsing
 */
static char *expr_ptr;
static int expr_tok;
static u_long expr_num;

#define TOK_NUM		0200
#define TOK_FUNC	0201
#define TOK_NE		0202
#define TOK_EQ		0203
#define TOK_ANDAND	0204

static int e_nexttok(void);
static long e(void);
static long e_comp(void);
static long e_arith(void);
static long e_prim(void);
static long e_term(void);
static long func_defined(char **);
static long func_eq(char **);
static long func_access(char **);
static long func_accessp(char **);
static long func_exec(char **);

static struct {
	char	*name;
	int	args;
	long	(*func)(char **);
} funcs[] = {
	{ "defined",	1, func_defined	},
	{ "eq",		2, func_eq	},
	{ "access",	2, func_access	},
	{ "exec",	1, func_exec },
	{ "accessp",	3, func_accessp },
	{ NULL, 0, 0 }
};

static char *
tokname(void)
{
	static char buf[100];

	if(expr_tok == EOF)
		return "EOF";
	if(isascii(expr_tok))
		sprintf(buf, "%c", expr_tok);

	else switch(expr_tok) {

	  case TOK_NUM:
		sprintf(buf, "NUM{%ld}", expr_num);
		break;

	  case TOK_FUNC:
		sprintf(buf, "%s", funcs[(int)expr_num].name);
		break;

	  case TOK_NE:
		sprintf(buf, "!=");
		break;

	  case TOK_EQ:
		sprintf(buf, "==");
		break;

	  case TOK_ANDAND:
		sprintf(buf, "&&");
		break;

	  default:
		sprintf(buf, "???{0%o}", expr_tok);
		break;
	}
	return buf;
}

static long
expr(char *str)
{
	long v;

	expr_ptr = str;
	e_nexttok();
	v = e();
	if(expr_tok != EOF)
		conf_panic("bad token in expression: '%s'", tokname());
	return v;
}

static int
e_nexttok()
{
	char *cp;
	int i;

	while(isascii(*expr_ptr) && isspace((int)*expr_ptr))
		expr_ptr++;
	if(*expr_ptr == '\0')
		return expr_tok = EOF;
	if(!isascii(*expr_ptr))
		conf_panic("illegal character 0x%02x in expression", *expr_ptr & 0377);

	if(*expr_ptr == '!' && expr_ptr[1] == '=') {
		expr_ptr += 2;
		return expr_tok = TOK_NE;
	}
	if(*expr_ptr == '=' && expr_ptr[1] == '=') {
		expr_ptr += 2;
		return expr_tok = TOK_EQ;
	}
	if(*expr_ptr == '&' && expr_ptr[1] == '&') {
		expr_ptr += 2;
		return expr_tok = TOK_ANDAND;
	}
	if(strchr("+-*/%(),!", *expr_ptr) != NULL)
		return expr_tok = *expr_ptr++;
	if(isdigit((int)*expr_ptr)) {
		expr_num = strtoul(expr_ptr, &expr_ptr, 0);
		return expr_tok = TOK_NUM;
	}
	if(isalpha((int)*expr_ptr)) {
		for(cp = expr_ptr; isascii(*cp) && isalpha((int)*cp); cp++)
			;
		for(i = 0; funcs[i].name; i++)
			if(cp - expr_ptr == (ptrdiff_t)strlen(funcs[i].name) &&
			   strncmp(expr_ptr, funcs[i].name, cp - expr_ptr) == 0)
				break;
		if(funcs[i].name) {
			expr_num = i;
			expr_ptr = cp;
			return expr_tok = TOK_FUNC;
		}
		conf_panic("unknown keyword in expression '%.*s'", cp - expr_ptr, expr_ptr);
	}
	conf_panic("illegal character 0x%02x '%c' in expression", *expr_ptr & 0377, *expr_ptr);
}

static char *
getarg(void)
{
	char *cp;
	char *str;

	while(*expr_ptr != '\0' && isascii(*expr_ptr) && isspace((int)*expr_ptr))
		expr_ptr++;
	for(cp = expr_ptr; *cp != '\0' && isascii(*cp) && *cp != ')' && *cp != ','; cp++)
		;
	str = xalloc(cp - expr_ptr + 1);
	strncpy(str, expr_ptr, cp - expr_ptr);
	str[cp - expr_ptr] = 0;

	expr_ptr = cp;

	strtrimsp(&str, 2);
	return str;
}

static long
e()
{
	long t;

	t = e_comp();
  loop:
	switch(expr_tok) {

	  case TOK_ANDAND:
		e_nexttok();
		t = t && e_comp();
		goto loop;

	  default:
		return t;
	}
}

static long
e_comp()
{
	long t;

	t = e_arith();
	switch(expr_tok) {

	  case TOK_EQ:
		e_nexttok();
		return t == e_arith();

	  case TOK_NE:
		e_nexttok();
		return t != e_arith();

	  default:
		return t;
	}
}

static long
e_arith()
{
	long t;

	t = e_term();
  loop:
	switch(expr_tok) {

	  case '+':
		e_nexttok();
		t += e_term();
		goto loop;

	  case '-':
		e_nexttok();
		t -= e_term();
		goto loop;

	  default:
		return t;
	}
}

static long
e_term()
{
	long t = e_prim();

  loop:
	switch(expr_tok) {

	  case '*':
		e_nexttok();
		t *= e_prim();
		goto loop;

	  case '/':
		e_nexttok();
		t /= e_prim();
		goto loop;

	  case '%':
		e_nexttok();
		t %= e_prim();
		goto loop;

	  default:
		return t;
	}
}

static long
e_prim()
{
	long t;
	int i, a;
	char *args[10];

	switch(expr_tok) {

	  case '!':
		e_nexttok();
		return !e_prim();

	  case '-':
		e_nexttok();
		return -e_prim();

	  case TOK_NUM:
		t = expr_num;
		e_nexttok();
		return t;

	  case TOK_FUNC:
		i = (int)expr_num;
		if(e_nexttok() != '(')
			conf_panic("need '(' after '%s'", funcs[i].name);
		for(a = 0; a < funcs[i].args; a++) {
			args[a] = getarg();
			if(a < funcs[i].args - 1 && e_nexttok() != ',')
				conf_panic("need ',' in '%s(...'", funcs[i].name);
		}
		if(e_nexttok() != ')')
			conf_panic("need ')' after 'defined(NAME'");
		e_nexttok();
		return (*funcs[i].func)(args);

	  case '(':
		e_nexttok();
		t = e();
		if(expr_tok != ')')
			conf_panic("expecting ')'");
		e_nexttok();
		return t;

	  default:
		conf_panic("bad token '%s'", tokname());
	}
}

static long
func_defined(char **args)
{
	check_name(args[0]);
	return find_name(args[0]) != NULL;
}

static long
func_eq(char **args)
{
	return (strcmp(args[0], args[1]) == 0) ? 1 : 0;
}

/*
 * Parse an access string for the access() and accessp() functions
 */
static int
parse_access_string(const char *arg)
{
	int mode = 0;

	while (*arg) {
		switch (*arg) {

		  case 'f': case 'F':
			mode |= F_OK;
			break;

		  case 'r': case 'R':
			mode |= R_OK;
			break;

		  case 'w': case 'W':
			mode |= W_OK;
			break;

		  case 'x': case 'X':
			mode |= X_OK;
			break;

		  default:
			conf_panic("bad access key '%c'", *arg);
		}
		arg++;
	}
	return (mode);
}

/*
 * Access function
 */
static long
func_access(char **args)
{
	return (access(args[0], parse_access_string(args[1])) != -1);
}

static long
func_accessp(char **args)
{
	int mode = parse_access_string(args[1]);
	int ret;
	char *path, *cur, *p, *fn;

	if (strchr(args[0], '/') != NULL)
		return (access(args[0], mode) != -1);

	cur = path = strsave(args[2]);
	ret = -1;
	while ((p = strsep(&cur, ":")) != NULL) {
		if (*p == '\0')
			p = ".";
		fn = xalloc(strlen(p) + strlen(args[0]) + 2);
		strcpy(fn, p);
		strcat(fn, "/");
		strcat(fn, args[0]);
		if ((ret = access(fn, mode)) != -1)
			break;
		free(fn);
	}
	free(path);
	return (ret != -1);

}

static long
func_exec(char **args)
{
	pid_t pid;
	int status;
	sigset_t mchld, msave;
	struct sigaction ign, ointr, oquit;

	ign.sa_handler = SIG_IGN;
	ign.sa_flags = 0;
	sigemptyset(&ign.sa_mask);

	if(sigaction(SIGINT, &ign, &ointr))
		panic("sigaction(SIGINT): %s", strerror(errno));
	if(sigaction(SIGQUIT, &ign, &oquit))
		panic("sigaction(SIGQUIT): %s", strerror(errno));

	sigemptyset(&mchld);
	sigaddset(&mchld, SIGCHLD);
	if(sigprocmask(SIG_BLOCK, &mchld, &msave))
		panic("sigprocmask(SIG_BLOCK): %s", strerror(errno));

	if((pid = fork()) < 0)
		panic("fork: %s", strerror(errno));

	if(pid == 0) {
		/* child */
		sigaction(SIGINT, &ointr, NULL);
		sigaction(SIGQUIT, &oquit, NULL);
		sigprocmask(SIG_SETMASK, &msave, NULL);

		execl(_PATH_BSHELL, "sh", "-c", args[0], NULL);
		_exit(127);
	}

	/* parent */
	while(waitpid(pid, &status, 0) < 0)
		if(errno != EINTR) {
			status = -1;
			break;
		}

	if(sigaction(SIGINT, &ointr, NULL))
		panic("sigaction(SIGINT): %s", strerror(errno));
	if(sigaction(SIGQUIT, &oquit, NULL))
		panic("sigaction(SIGQUIT): %s", strerror(errno));
	if(sigprocmask(SIG_SETMASK, &msave, NULL))
		panic("sigprocmask(SIG_SETMASK): %s", strerror(errno));

	return status;
}

void
panic(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);

	if (daemonized)
		vsyslog(LOG_ERR, fmt, ap);
	else
		vpanic(fmt, ap);
	va_end(ap);
	exit(1);
}

/*
 * Search a path list for a file.
 *
 * If the filename contains at least one slash, we assume, that this
 * is the complete pathname to the script and try to open it. If not,
 * we loop through the library path and try each element, until one succeedes.
 */
static FILE *
path_open(const char *fname, char **fn, const char *ipath)
{
	FILE *fp;
	char *cur, *p, *path;

	if (strchr(fname, '/') != NULL) {
		if ((fp = fopen(fname, "r")) != NULL)
			*fn = strsave(fname);
		return (fp);
	}

	cur = path = strsave(ipath);
	fp = NULL;
	errno = ENOENT;
	while ((p = strsep(&cur, ":")) != NULL) {
		if (*p == '\0')
			p = ".";
		*fn = xalloc(strlen(p) + strlen(fname) + 2);
		strcpy(*fn, p);
		strcat(*fn, "/");
		strcat(*fn, fname);
		if ((fp = fopen(*fn, "r")) != NULL)
			break;
		free(*fn);
	}
	free(path);
	return (fp);
}

/*
 * Open a file in the library path
 *
 */
FILE *
lib_fopen(const char *fname, char **fn)
{
	return (path_open(fname, fn, globopts.libpath));
}
int
lib_open(const char *fname, char **fn)
{
	int fd;
	char *path, *cur, *p;

	if (strchr(fname, '/') != NULL) {
		if ((fd = open(fname, O_RDONLY)) != -1)
			*fn = strsave(fname);
		return (fd);
	}

	cur = path = strsave(globopts.libpath);
	fd = -1;
	errno = ENOENT;
	while ((p = strsep(&cur, ":")) != NULL) {
		if (*p == '\0')
			p = ".";
		*fn = xalloc(strlen(p) + strlen(fname) + 2);
		strcpy(*fn, p);
		strcat(*fn, "/");
		strcat(*fn, fname);
		if ((fd = open(*fn, O_RDONLY)) != -1)
			break;
		free(*fn);
	}
	free(path);
	return (fd);
}
