/*	edparams 1.3 - Modify boot parameters		Author: Kees J. Bot
 */

#define nil 0
#ifndef sun
#define _POSIX_SOURCE	1
#define _MINIX		1
#endif
#include <stddef.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#if _POSIX_TTY_CODE
#include <termios.h>
#else
#include <sgtty.h>
#endif

#define SECTOR_SIZE	512
#define SECTOR_SHIFT	9
#define RATIO		(BLOCK_SIZE / SECTOR_SIZE)
#define HRATIO		(SECTOR_SIZE / HCLICK_SIZE)

#define PARAMSEC	1	/* Sector containing boot parameters. */

#define ESC		1234	/* Escape key (interrupt). */

#define get_tick()	time((time_t *) nil)
#define MSEC_PER_TICK	1000	/* Resolution of time(2). */

int device;			/* Device to edit parameters. */
char *devname;			/* Name of device. */

#if _POSIX_TTY_CODE
struct termios termios;
#else
struct sgttyb ttyb;
#endif
int istty= 1;

void report(label) char *label;
/* edparams: label: No such file or directory */
{
	int e= errno;
	fprintf(stderr, "edparams: ");
	fflush(stderr);
	errno= e;
	perror(label);
}

void fatal(label) char *label;
{
	report(label);
	exit(1);
}

void *alloc(m, n) void *m; size_t n;
{
	m= m == nil ? malloc(n) : realloc(m, n);
	if (m == nil) fatal("");
	return m;
}

char null[]= "";

void sfree(s) char *s;
/* Free a non-null string. */
{
	if (s != nil && s != null) free((void *) s);
}

char *copystr(s) char *s;
/* Copy a non-null string using malloc. */
{
	char *c;

	if (*s == 0) return null;
	c= (char *) alloc((void *) nil, (strlen(s) + 1) * sizeof(char));
	strcpy(c, s);
	return c;
}

int getch()
{
	char c;

	switch (read(0, &c, 1)) {
	case -1:
		if (errno != EINTR) fatal("");
		return(ESC);
	case 0:
		if (istty) putchar('\n');
		exit(0);
	case 1:
		return c & 0xFF;
	}
}

/* Sticky attributes. */
#define E_SPECIAL	0x01	/* These are known to the program. */
#define E_DEV		0x02	/* The value is a device name. */
#define E_RESERVED	0x04	/* May not be set by user, e.g. scancode */
#define E_STICKY	0x07	/* Don't go once set. */
/* Volatile attributes. */
#define E_VAR		0x08	/* Variable */
#define E_FUNCTION	0x10	/* Function definition. */

typedef struct environment {
	struct environment *next;
	char	flags;
	char	*name;
	char	*arg;
	char	*value;
	char	*defval;	/* Safehouse for default values. */
} environment;

environment *env;		/* Lists the environment. */

#define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
#define arraylimit(a)	((a) + arraysize(a))

char *readline()
/* Read a line including a newline with echoing. */
{
	char *line;
	size_t i= 0, z;
	int c;

	line= (char *) alloc((void *) nil, (z= 20) * sizeof(char));

	do {
		c= getch();

		if (c < ' ' && c != '\n') {
			printf("\7Don't use control characters!\n");
			while ((c= getch()) != '\n') {}
			i= 0;
		} else {
			line[i++]= c;
			if (i == z) line= (char *) alloc((void *) line,
						(z*= 2) * sizeof(char));
		}
	} while (c != '\n');
	line[i]= 0;
	return line;
}

int sugar(tok) char *tok;
/* Recognize special tokens. */
{
	return strchr("=(){};\n", tok[0]) != nil;
}

char *onetoken(aline, arg) char **aline; int arg;
/* Returns a string with one token for tokenize.  Arg is true when reading
 * between ( and ).
 */
{
	char *line= *aline;
	size_t n;
	char *tok;

	/* Skip spaces. */
	while (*line == ' ') line++;

	*aline= line;

	/* Don't do odd junk (nor the terminating 0!). */
	if ((unsigned) *line < ' ' && *line != '\n') return nil;

	if (arg) {
		/* Function argument, everything goes except ). */
		int depth= 0;

		while ((unsigned) *line >= ' ') {
			if (*line == '(') depth++;
			if (*line == ')' && --depth < 0) break;
			line++;
		}
		while (line > *aline && line[-1] == ' ') line--;
	} else
	if (sugar(line)) {
		/* Single character token. */
		line++;
	} else {
		/* Multicharacter token. */
		do line++; while ((unsigned) *line > ' ' && !sugar(line));
	}
	n= line - *aline;
	tok= (char *) alloc((void *) nil, (n + 1) * sizeof(char));
	memcpy((void *) tok, (void *) *aline, n);
	tok[n]= 0;
	if (tok[0] == '\n') tok[0]= ';';	/* ';' same as '\n' */

	*aline= line;
	return tok;
}

/* Typed commands form strings of tokens. */

typedef struct token {
	struct token	*next;	/* Next in a command chain. */
	char		*token;
} token;

token **tokenize(acmds, line, fundef)
	token	**acmds;	/* Splice tokenized line into this list. */
	char	*line;		/* Characters to be tokenized. */
	int	*fundef;	/* Keeps state when forming a function def. */
/* Takes a line apart to form tokens.  The tokens are inserted into a command
 * chain at *acmds.  Tokenize returns a reference to where another line could
 * be added.  The fundef variable holds the state tokenize is in when decoding
 * a multiline function definition.  It is nonzero when more must be read.
 * Tokenize looks at spaces as token separators, and recognizes only
 * ';', '=', '(', ')' '{', '}', and '\n' as single character tokens.
 */
{
	int fd= *fundef;
	char *tok;
	token *newcmd;
	static char funsugar[]= { '(', 0, ')', '{', '}' };

	while ((tok= onetoken(&line, fd == 1)) != nil) {
		if (fd == 1)
			fd++;	/* Function argument. */
		else
		if (funsugar[fd] == tok[0]) {
			/* Recognize next token as part of a function def. */
			fd= tok[0] == '}' ? 0 : fd + 1;
		} else
		if (fd != 0) {
			if (tok[0] == ';' && fd == 3) {
				/* Kill separator between ')' and '{'. */
				free((void *) tok);
				continue;
			}
			/* Syntax error unless between '{' and '}'. */
			if (fd != 4) fd= 0;
		}
		newcmd= (token *) alloc((void *) nil, sizeof(*newcmd));
		newcmd->token= tok;
		newcmd->next= *acmds;
		*acmds= newcmd;
		acmds= &newcmd->next;
	}
	*fundef= fd;
	return acmds;
}

token *cmds= nil;	/* String of commands to execute. */

char *poptoken()
/* Pop one token off the command chain. */
{
	token *cmd= cmds;
	char *tok= cmd->token;

	cmds= cmd->next;
	free((void *) cmd);

	return tok;
}

void voidtoken()
/* Remove one token from the command chain. */
{
	free((void *) poptoken());
}

void void_cmds()
/* Void the whole list. */
{
	while (cmds != nil) voidtoken();
}

int trapsig;

void trap(sig) int sig;
{
	trapsig= sig;
	signal(sig, trap);
	alarm(1);
}

void interrupt()
/* Clean up after an ESC has been typed. */
{
	printf("[ESC]\n");
	trapsig= 0;

	/* Delete leftover commands. */
	void_cmds();
}

int is_default(e) environment *e;
{
	return (e->flags & E_SPECIAL) && e->defval == nil;
}

environment **searchenv(name) char *name;
{
	environment **aenv= &env;

	while (*aenv != nil && strcmp((*aenv)->name, name) != 0)
		aenv= &(*aenv)->next;

	return aenv;
}

#define b_getenv(name)	(*searchenv(name))
/* Return the environment *structure* belonging to name, or nil if not found. */

char *b_value(name) char *name;
/* The value of a variable. */
{
	environment *e= b_getenv(name);

	return e == nil || !(e->flags & E_VAR) ? nil : e->value;
}

char *b_body(name) char *name;
/* The value of a function. */
{
	environment *e= b_getenv(name);

	return e == nil || !(e->flags & E_FUNCTION) ? nil : e->value;
}

int b_setenv(flags, name, arg, value) int flags; char *name, *arg, *value;
/* Change the value of an environment variable.  Returns the flags of the
 * variable if you are not allowed to change it, 0 otherwise.
 */
{
	environment **aenv, *e;

	if (*(aenv= searchenv(name)) == nil) {
		e= (environment *) alloc((void *) nil, sizeof(*e));
		e->name= copystr(name);
		e->flags= flags;
		e->defval= nil;
		e->next= nil;
		*aenv= e;
	} else {
		e= *aenv;

		/* Don't touch reserved names and don't change special
		 * variables to functions or vv.
		 */
		if (e->flags & E_RESERVED || (e->flags & E_SPECIAL
			&& (e->flags & E_FUNCTION) != (flags & E_FUNCTION)
		)) return e->flags;

		e->flags= (e->flags & E_STICKY) | flags;
		if (is_default(e))
			e->defval= e->value;
		else
			sfree(e->value);
		sfree(e->arg);
	}
	e->arg= copystr(arg);
	e->value= copystr(value);
	return 0;
}

int b_setvar(flags, name, value) int flags; char *name, *value;
/* Set variable or simple function. */
{
	return b_setenv(flags, name, null, value);
}

void b_unset(name) char *name;
/* Remove a variable from the environment.  A special variable is reset to
 * its default value.
 */
{
	environment **aenv, *e;

	if ((e= *(aenv= searchenv(name))) == nil) return;

	if (e->flags & E_SPECIAL) {
		if (e->defval != nil) {
			sfree(e->arg);
			e->arg= null;
			sfree(e->value);
			e->value= e->defval;
			e->defval= nil;
		}
	} else {
		sfree(e->name);
		sfree(e->arg);
		sfree(e->value);
		*aenv= e->next;
		free((void *) e);
	}
}

#define between(a, c, z)	((unsigned) ((c) - (a)) <= ((z) - (a)))

long a2l(a) char *a;
/* Cheap atol(). */
{
	int sign= 1;
	long l= 0;

	if (*a == '-') { sign= -1; a++; }

	while (between('0', *a, '9')) l= l * 10 + (*a++ - '0');

	return sign * l;
}

char *ul2a(n) unsigned long n;
/* Transform a long number to ascii digits. */
{
	static char num[3 * sizeof(n)];
	char *pn= arraylimit(num) - 1;

	do *--pn = (n % 10) + '0'; while ((n/= 10) > 0);
	return pn;
}

char *u2a(n) unsigned n;
/* Transform a short number to ascii digits. */
{
	return ul2a((unsigned long) n);
}

void get_parameters()
{
	char params[SECTOR_SIZE + 1];
	token **acmds;
	int fundef= 0;

	/* Variables that Minix needs: */
	b_setvar(E_SPECIAL|E_VAR|E_DEV, "rootdev", "ram");
	b_setvar(E_SPECIAL|E_VAR|E_DEV, "ramimagedev", "bootdev");
	b_setvar(E_SPECIAL|E_VAR, "ramsize", "0");
	b_setvar(E_SPECIAL|E_VAR, "keyboard", "standard");
	b_setvar(E_SPECIAL|E_VAR, "processor", null);
	b_setvar(E_SPECIAL|E_VAR, "memsize", null);
	b_setvar(E_SPECIAL|E_VAR, "emssize", null);
	b_setvar(E_SPECIAL|E_VAR, "chrome", null);
	b_setvar(E_SPECIAL|E_VAR, "video", null);

	/* Variables boot needs: */
	b_setvar(E_SPECIAL|E_VAR, "image", "minix");
	b_setvar(E_SPECIAL|E_FUNCTION, "main", "menu");

	/* Default menu function: */
	b_setenv(E_RESERVED|E_FUNCTION, "\1", "=,Start Minix", "boot");

	/* Reserved names: */
	b_setvar(E_RESERVED, "scancode", null);
	b_setvar(E_RESERVED, "boot", null);
	b_setvar(E_RESERVED, "menu", null);
	b_setvar(E_RESERVED, "set", null);
	b_setvar(E_RESERVED, "unset", null);
	b_setvar(E_RESERVED, "save", null);
	b_setvar(E_RESERVED, "ls", null);
	b_setvar(E_RESERVED, "echo", null);
	b_setvar(E_RESERVED, "trap", null);

	memset((void *) params, 0, sizeof(params));

	/* Tokenize bootparams sector. */
	if (lseek(device, (off_t) PARAMSEC * SECTOR_SIZE, SEEK_SET) < 0
				|| read(device, params, SECTOR_SIZE) < 0)
		fatal(devname);
	else {
		acmds= tokenize(&cmds, params, &fundef);

		/* Syntax check */
		(void) tokenize(acmds, ":;", &fundef);
	}
}

void addparm(ap, n) char **ap, *n;
{
	char *p= *ap;
	while (*n != 0 && *p != 0) *p++ = *n++;
	*ap= p;
}

void save_parameters()
/* Save nondefault environment variables to the bootparams sector. */
{
	environment *e;
	char params[SECTOR_SIZE + 1];
	char *p= params;

	/* Default filling: */
	memset((void *) params, '\n', (size_t) SECTOR_SIZE);

	/* Don't touch the 0! */
	params[SECTOR_SIZE]= 0;

	for (e= env; e != nil; e= e->next) {
		if (e->flags & E_RESERVED || is_default(e)) continue;

		addparm(&p, e->name);
		if (e->flags & E_FUNCTION) {
			addparm(&p, "(");
			addparm(&p, e->arg);
			addparm(&p, "){");
		} else {
			addparm(&p, (e->flags & (E_DEV|E_SPECIAL)) != E_DEV
							? "=" : "=d ");
		}
		addparm(&p, e->value);
		if (e->flags & E_FUNCTION) addparm(&p, "}");
		if (*p == 0) {
			printf("The environment is too big\n");
			return;
		}
		*p++= '\n';
	}
	/* Save the parameters on disk. */
	if (lseek(device, (off_t) PARAMSEC * SECTOR_SIZE, SEEK_SET) < 0
				|| write(device, params, SECTOR_SIZE) < 0) {
		report(devname);
		printf("Can't save environment\n");
	}
}

void show_env()
/* Show the environment settings. */
{
	environment *e;

	for (e= env; e != nil; e= e->next) {
		if (e->flags & E_RESERVED) continue;

		if (e->flags & E_FUNCTION)
			printf("%s(%s) {%s}", e->name, e->arg, e->value);
		else {
			printf("%s = ", e->name);
			if (is_default(e)) putchar('(');
			printf("%s", e->value);
			if (is_default(e)) putchar(')');
		}
		putchar('\n');
	}
}

int numeric(s) char *s;
/* True iff s is a string of digits. */
{
	char *n= s;

	while (between('0', *n, '9')) n++;

	return n != s && *n == 0;
}

char *Thandler;
unsigned long Tbase, Tcount;

void unschedule()
/* Invalidate a waiting command. */
{
	alarm(0);

	if (Thandler == nil) {
		free((void *) Thandler);
		Thandler= nil;
	}
}

void schedule(msec, cmd) long msec; char *cmd;
/* Schedule command at a certain time from now. */
{
	unschedule();
	Thandler= cmd;
	Tbase= get_tick();
	Tcount= (msec + MSEC_PER_TICK - 1) / MSEC_PER_TICK;
	alarm(1);
}

int expired()
/* Check if the timer expired.  If so prepend the scheduled command to
 * the command chain and return 1.
 */
{
	int fundef= 0;

	if (Thandler == nil || (get_tick() - Tbase) < Tcount) return 0;

	(void) tokenize(tokenize(&cmds, Thandler, &fundef), ";", &fundef);
	unschedule();
	return 1;
}

int delay(msec) char *msec;
/* Delay for a given time.  Returns true iff delay was not interrupted.
 * Make sure get_tick is not called for nonpositive msec, because get_tick
 * may do funny things on the original IBM PC (not the XT!).
 * If msec happens to be the string "swap" then wait till the user hits
 * return after changing diskettes.
 */
{
	int swap= 0;
	unsigned long base, count;

	if (strcmp(msec, "swap") == 0) {
		swap= 1;
		count= 0;
		printf("\nInsert the root diskette then hit RETURN\n");
	} else
	if ((count= a2l(msec)) > 0) {
		count/= MSEC_PER_TICK;
		base= get_tick();
	}

	alarm(1);	/* Let the "counter" run. */

	do {
		if (swap) {
			switch (getch()) {
			case ESC:	interrupt(); return 0;
			case '\n':	swap= 0;
			}
		} else
		if (count > 0) {
			pause();
			if (trapsig == SIGINT) interrupt();
		}
	} while (!expired()
		&& (swap || (count > 0 && (get_tick() - base) < count))
	);
	return 1;
}

enum whatfun { NOFUN, SELECT, DEFFUN, USERFUN } menufun(e) environment *e;
{
	if (!(e->flags & E_FUNCTION) || e->arg[0] == 0) return NOFUN;
	if (e->arg[1] != ',') return SELECT;
	return e->flags & E_RESERVED ? DEFFUN : USERFUN;
}

void menu()
{
	int fundef= 0, c, def= 1;
	char *cmd= nil;
	environment *e;
#if _POSIX_TTY_CODE
	struct termios rawterm;

	rawterm= termios;
	rawterm.c_lflag &= ~(ICANON|ECHO);
	if (tcsetattr(0, TCSANOW, &rawterm) < 0) fatal("");
#else
	struct sgttyb rawttyb;

	rawttyb= ttyb;
	rawttyb.sg_flags= (rawttyb.sg_flags | CBREAK) & ~ECHO;
	if (ioctl(0, TIOCSETP, &rawttyb) < 0) fatal("");
#endif

	/* Just a default menu? */
	for (e= env; e != nil; e= e->next) if (menufun(e) == USERFUN) def= 0;

	printf("\nHit a key as follows:\n\n");

	for (e= env; e != nil; e= e->next) {
		switch (menufun(e)) {
		case DEFFUN:
			if (!def) break;
		case USERFUN:
			printf("    %c  %s\n", e->arg[0], e->arg+2);
			break;
		case SELECT:
			printf("    %c  Select %s kernel\n", e->arg[0],e->name);
		}
	}
	do {
		if ((c= getch()) == ESC) {
			if (trapsig == SIGINT) { interrupt(); goto ret; }
			if (expired()) goto ret;
		}

		for (e= env; e != nil; e= e->next) {
			switch (menufun(e)) {
			case DEFFUN:
				if (!def) break;
			case USERFUN:
			case SELECT:
				if (c == e->arg[0]) cmd= e->value;
			}
		}
	} while (cmd == nil);

	printf("%c\n", c);
	(void) tokenize(&cmds, cmd, &fundef);
ret:
#if _POSIX_TTY_CODE
	if (tcsetattr(0, TCSANOW, &termios) < 0) fatal("");
#else
	if (ioctl(0, TIOCSETP, &ttyb) < 0) fatal("");
#endif
}

void execute()
/* Get one command from the command chain and execute it.  Legal commands are
 * listed at the end.
 */
{
	token *second, *third, *fourth, *fifth, *sep;
	char *name= cmds->token;
	size_t n= 0;

	/* There must be a separator lurking somewhere. */
	for (sep= cmds; sep != nil && sep->token[0] != ';'; sep= sep->next) n++;

	if ((second= cmds->next) != nil
		&& (third= second->next) != nil
		&& (fourth= third->next) != nil)
			fifth= fourth->next;

		/* Null command? */
	if (n == 0) {
		voidtoken();
		return;
	} else
		/* name = [device] value? */
	if ((n == 3 || n == 4)
		&& !sugar(name)
		&& second->token[0] == '='
		&& !sugar(third->token)
		&& (n == 3 || (n == 4 && third->token[0] == 'd'
					&& !sugar(fourth->token)
	))) {
		char *value= third->token;
		int flags= E_VAR;

		if (n == 4) { value= fourth->token; flags|= E_DEV; }

		if ((flags= b_setvar(flags, name, value)) != 0) {
			printf("%s is a %s\n", name,
				flags & E_RESERVED ? "reserved word" :
						"special function");
			void_cmds();
		} else
			while (cmds != sep) voidtoken();
		return;
	} else
		/* name '(' arg ')' '{' ... '}'? */
	if (n >= 5
		&& !sugar(name)
		&& second->token[0] == '('
		&& fourth->token[0] == ')'
		&& fifth->token[0] == '{'
	) {
		token *fun= fifth->next;
		int ok= 1, flags;
		char *body;
		size_t len= 1;

		sep= fun;
		while (sep != nil && sep->token[0] != '}') {
			len+= strlen(sep->token) + 1;
			sep= sep->next;
		}
		if (sep == nil || (sep= sep->next) == nil
			|| sep->token[0] != ';'
		) ok= 0;

		if (ok) {
			body= (char *) alloc((void *) nil, len * sizeof(char));
			*body= 0;

			while (fun->token[0] != '}') {
				strcat(body, fun->token);
				if (!sugar(fun->token)
					&& !sugar(fun->next->token)
				) strcat(body, " ");
				fun= fun->next;
			}

			if ((flags= b_setenv(E_FUNCTION, name,
						third->token, body)) != 0) {
				printf("%s is a %s\n", name,
					flags & E_RESERVED ? "reserved word" :
							"special variable");
				void_cmds();
			} else
				while (cmds != sep) voidtoken();
			free((void *) body);
			return;
		}
	} else
		/* Command coming up, check if ESC typed. */
	if (trapsig == SIGINT) {
		interrupt();
		return;
	} else
		/* unset name ..., echo word ...? */
	if (n >= 1 && (
		strcmp(name, "unset") == 0
		|| strcmp(name, "echo") == 0
	)) {
		int cmd= name[0];
		char *arg= poptoken();

		for (;;) {
			free((void *) arg);
			if (cmds == sep) break;
			arg= poptoken();
			if (cmd == 'u')
				b_unset(arg);
			else {
				printf("%s", arg);
				if (cmds != sep) putchar(' ');
			}
		}
		if (cmd == 'e') putchar('\n');
		return;
	} else
		/* boot device, ls dir, delay msec? */
	if (n == 2 && (
		strcmp(name, "boot") == 0
		|| strcmp(name, "delay") == 0
		|| strcmp(name, "ls") == 0
	)) {
		int cmd= name[0];
		char *arg;

		voidtoken();
		arg= poptoken();
		switch (cmd) {
		case 'b':	printf("[boot %s]\n", arg); break;
		case 'd':	(void) delay(arg); break;
		case 'l':	printf("[ls %s]\n", arg);
		}
		free((void *) arg);
		return;
	} else
		/* trap msec command? */
	if (n == 3 && strcmp(name, "trap") == 0 && numeric(second->token)) {
		long msec= a2l(second->token);

		voidtoken();
		voidtoken();
		schedule(msec, poptoken());
		return;
	} else
		/* Simple command. */
	if (n == 1) {
		char *cmd= poptoken();
		char *body;
		int fundef= 0;
		int ok= 0;

		if (strcmp(cmd, "boot") == 0) { printf("[boot]\n"); ok= 1; }
		if (strcmp(cmd, "delay") == 0) { (void) delay("500"); ok= 1; }
		if (strcmp(cmd, "ls") == 0) { printf("[ls]\n"); ok= 1; }
		if (strcmp(cmd, "menu") == 0) { menu(); ok= 1; }
		if (strcmp(cmd, "save") == 0) { save_parameters(); ok= 1; }
		if (strcmp(cmd, "set") == 0) { show_env(); ok= 1; }

		/* Command to check bootparams: */
		if (strcmp(cmd, ":") == 0) ok= 1;

		/* User defined function. */
		if (!ok && (body= b_body(cmd)) != nil) {
			(void) tokenize(&cmds, body, &fundef);
			ok= 1;
		}
		if (!ok) printf("%s: unknown function", cmd);
		free((void *) cmd);
		if (ok) return;
	} else {
		/* Syntax error. */
		printf("Can't parse:");
		while (cmds != sep) {
			printf(" %s", cmds->token); voidtoken();
		}
	}

	/* Getting here means that the command is not understood. */
	printf("\nLegal commands:\n");
	printf("    name = [device] value   - Set environment variable\n");
	printf("    name(arg) { ... }       - Define function\n");
	printf("    name                    - Call function\n");
	printf("    boot [device]           - Boot Minix or another O.S.\n");
	printf("    delay [msec]            - Delay (500 msec default)\n");
	printf("    echo word ...           - Print these words\n");
	printf("    ls [directory]          - List contents of directory\n");
	printf("    menu                    - Simple menu driven startup\n");
	printf("    save                    - Save environment\n");
	printf("    set                     - Show environment\n");
	printf("    trap msec command       - Schedule command\n");
	printf("    unset name ...          - Unset environment variables\n");
	void_cmds();
}

void monitor()
/* Read one or more lines and tokenize them. */
{
	char *line;
	int fundef= 0;
	token **acmds= &cmds;

	unschedule();	/* Kill a trap. */

	do {
		if (istty) putchar(fundef == 0 ? '>' : '+');
		fflush(stdout);
		line= readline();
		acmds= tokenize(acmds, line, &fundef);
		free((void *) line);
	} while (fundef != 0);
}

void main(argc, argv) int argc; char **argv;
/* Do not load and start anything, just edit parameters. */
{
	int i= 2;

#if _POSIX_TTY_CODE
	if (tcgetattr(0, &termios) < 0) istty= 0;
#else
	if (ioctl(0, TIOCGETP, &ttyb) < 0) istty= 0;
#endif
	if (argc > 2) istty= 0;

	if (argc < 2) {
		fprintf(stderr, "Usage: edparams device [command ...]\n");
		exit(1);
	}

	/* Print greeting message.  */
	if (istty) printf("Boot parameters editor.\n");

	signal(SIGINT, trap);
	signal(SIGALRM, trap);
#ifdef sun
	/* Grumble */ {
	struct sigvec vec;
	sigvec(SIGINT, (struct sigvec *) nil, &vec);
	vec.sv_flags|= SV_INTERRUPT;
	sigvec(SIGINT, &vec, (struct sigvec *) nil);
	sigvec(SIGALRM, (struct sigvec *) nil, &vec);
	vec.sv_flags|= SV_INTERRUPT;
	sigvec(SIGALRM, &vec, (struct sigvec *) nil);
	}
#endif

	devname= argv[1];
	if ((device= open(devname, O_RDWR | O_CREAT, 0666)) < 0)
		fatal(devname);

	/* Get environment variables from the parameter sector. */
	get_parameters();

	for (;;) {
		/* While there are commands, execute them! */
		while (cmds != nil || i < argc) {
			if (cmds == nil) {
				/* A command line command. */
				int fundef= 0;
				(void) tokenize(&cmds, argv[i++], &fundef);
			}
			execute();
			(void) expired();
		}

		/* Commands on the command line? */
		if (argc > 2) break;

		/* The "monitor" is just a "read one command" thing. */
		monitor();
	}
	exit(0);
}
/* Kees J. Bot 20-5-92. */
