/*
 * Copyright (C) 2000-2003 ASANO Masahiro
 */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#include "crash.h"

FILE *fp;

int argcnt;
char *args[MAXARGS];
char usage[1024];

PRIVATE int func_argcnt;
PRIVATE char *func_args[MAXARGS];

int arghist_ptr;
char *arghist_buf[ARGHIST];

struct aliastable *aliastable;

struct variabletable *variabletable;
int variabletable_size;

const char *except_message;
char except_message_buf[256];
jmp_buf *except_jmpbuf;

sigset_t block_mask, unblock_mask;

char PS1[] = "> ";
char PS2[] = "? ";

int sigpiped;

PRIVATE char *pipecmd;
PRIVATE char *outfile;
PRIVATE addr_t lastvalue;

PRIVATE void run_commands();

char *symname;
char *physname;
#ifdef CONF_USE_LKCD
char *kerntype;
#endif /*CONF_USE_LKCD*/

int quiet = 0;
int noprompt = 0;

void
sighandler(signo)
	int signo;
{
	switch (signo) {
	case SIGINT:
		THROW("Interrupted");
	case SIGPIPE:
		sigpiped = 1;
		return;
	default:
		THROWF("Unknown signal(%d)", signo);
	}
}

void
init()
{
	struct sigaction act;

#ifdef DEBUG
	if (sizeof(addr_t) < sizeof(void *)) {
		mprintf("Error: addr_t\n");
		exit(1);
	}
#endif

	/* open memory device */
	init_mem();

	/* for set-uid-execution */
	if (getuid() != 0 && geteuid() == 0) {
		/* take away privilege */
		seteuid(getuid());
	}

	if (!quiet) {
		introduce_myself();
	}

	/* get kernel symbol */
	init_sym();

	/* aliases and functiones */
	init_command();
	init_alias();
	init_function();

	/* GNU readline initialize */
	init_console();

	/* signal initialize */
	if (sigemptyset(&block_mask)) {
		perror("sigemptyset");
		exit(1);
	}
	if (sigaddset(&block_mask, SIGINT) || sigaddset(&block_mask, SIGPIPE)) {
		perror("sigaddset");
		exit(1);
	}
	if (sigprocmask(SIG_BLOCK, &block_mask, &unblock_mask)) {
		perror("sigprocmask");
		exit(1);
	}
	act.sa_handler = sighandler;
	act.sa_flags = 0;
	act.sa_mask = block_mask;
	if (sigaction(SIGINT,  &act, NULL) || sigaction(SIGPIPE,  &act, NULL)) {
		perror("sigaction");
		exit(1);
	}

	/* linux version dependent initialize */
	vers_init();

	if (!isatty(0)) {
		noprompt = 1;
	}
	if (!quiet) {
		mprintf("Type `?' for a list of commands.\n");
	}
}

void
setarghist(arg)
	const unsigned char *arg;
{
	int i;

	if (arg[0] == '\0')
		return;
	for (i = strlen(arg) - 1; i >= 0; i--)
		if (arg[i] != '_' && !isalnum(arg[i]))
			return;

	for (i = 0; i < ARGHIST; i++) {
		if (arghist_buf[i] && strcmp(arghist_buf[i], arg) == 0)
			break;
	}
	if (i < ARGHIST) {
		/* found */
		char *save = arghist_buf[i];
		if (i >= arghist_ptr) {
			for (i = arghist_ptr; i < ARGHIST - 1; i++)
				arghist_buf[i] = arghist_buf[i + 1];
			arghist_buf[ARGHIST - 1] = arghist_buf[0];
			i = 0;
		}
		for (; i < arghist_ptr - 1; i++)
			arghist_buf[i] = arghist_buf[i + 1];
		if (arghist_ptr)
			arghist_buf[arghist_ptr - 1] = save;
		else
			arghist_buf[ARGHIST - 1] = save;
		return;
	}
	if (arghist_ptr >= ARGHIST)
		arghist_ptr = 0;
	if (arghist_buf[arghist_ptr])
		free(arghist_buf[arghist_ptr]);
	arghist_buf[arghist_ptr++] = strdup(arg);
}

addr_t
dofunction(funcno)
	int funcno;
{
	int i;
	char *sv_args[MAXARGS];
	int sv_argcnt;

	for (i = 0; i < MAXARGS; i++) {
		sv_args[i] = func_args[i];
	}
	sv_argcnt = func_argcnt;

	for (i = 0; i < argcnt; i++) {
		func_args[i] = strdup(args[i]);
	}
	func_args[argcnt] = NULL;
	func_argcnt = argcnt;

	run_commands(functiontable[funcno].stmt);

	for (i = 0; i < argcnt; i++) {
		if (func_args[i]) {
			free(func_args[i]);
		}
	}
	for (i = 0; i < MAXARGS; i++) {
		func_args[i] = sv_args[i];
	}
	func_argcnt = sv_argcnt;
	return 0;
}

const char *
getvariable(name)
	const char *name;
{
	int i;
	static char buf[1024];

	for (i = 0; i < variabletable_size; i++) {
		if (strcmp(name, variabletable[i].name) == 0)
			return variabletable[i].value;
	}

	if (name[1] == 0) {
		if (*name == '?') {
			sprintf(buf, "%lx", lastvalue);
			return buf;
		} else if (*name == '$') {
			sprintf(buf, "%x", getpid());
			return buf;
		} else if (*name == '#') {
			sprintf(buf, "%x", func_argcnt);
			return buf;
		} else if (*name == '*') {
			buf[0] = '\0';
			for (i = 1; i < func_argcnt; i++) {
				if (i > 1) {
					strcat(buf, " ");
				}
				strcat(buf, func_args[i]);
			}
			return buf;
		} else if (isdigit(*name)) {
			i = (int)strtol(name, NULL, 0);
			if (i < 0 || i >= func_argcnt)
				return NULL;
			return func_args[i];
		}
	}
	if (*name == '-') {
		i = (int)strtol(name + 1, NULL, 0);
		if (i > 0 && i <= MAXARGS) {
			i = xaddr_ptr - i;
			if (i < 0) {
				i += MAXARGS;
			}
			sprintf(buf, "%lx", xaddr[i]);
			return buf;
		}
	}
	return getenv(name);
}

PRIVATE int
getnamelength(in)
	char *in;
{
	int i = 0;

	while (*in && (isalnum(*in) || *in == '_')) {
		i++;
		in++;
	}
	if (i == 0) {
		if (*in == '$' || *in == '*' || *in == '?' || *in == '#') {
			return 1;
		}
		if (*in == '-') {
			in++;
			while (*in && (isdigit(*in))) {
				i++;
				in++;
			}
			if (i > 0)
				return i + 1;
			else
				return 0;
		}
	}
	return i;
}

PRIVATE int
setargs(in, next)
	char *in;
	char **next;
{
	int j, set, endc;
	char *curp;
	const char *p;
	static char argument_split_buffer[1024];
	char name[32];

	if (next)
		*next = NULL;
	argcnt = 0;
	pipecmd = NULL;
	outfile = NULL;

	if (in == NULL) {
		return -1;
	}
	while (*in && isspace(*in)) {
		in++;
	}
	args[argcnt] = curp = argument_split_buffer;
	set = 0;
	for (;;) {
		switch (*in) {
		case 0:
		case '#':
			if (set) {
				*curp++ = '\0';
				argcnt++;
			}
			goto out;
		case ';':
			if (set) {
				*curp++ = '\0';
				argcnt++;
			}
			if (next)
				*next = in + 1;
			goto out;
		case ' ':
			if (set) {
				*curp++ = '\0';
				if (++argcnt >= MAXARGS - 1) {
					fprintf(stderr, "too many arguments\n");
					return -1;
				}
				args[argcnt] = curp;
			}
			in++;
			while (*in && isspace(*in)) {
				in++;
			}
			if (*in == 0)
				goto out;
			set = 0;
			break;
		case '!': case '|':
			if (set) {
				*curp++ = '\0';
				argcnt++;
			}
			while (*++in == ' ')
				;
			pipecmd = in;
			goto out;
		case '>':
			if (in[1] == '>') {
				set = 1;
				*curp++ = '>';
				*curp++ = '>';
				in += 2;
				break;
			}
			if (set) {
				*curp++ = '\0';
				argcnt++;
			}
			while (*++in == ' ')
				;
			outfile = in;
			goto out;
		case '\\':
			set = 1;
			if (*++in) {
				set = 1;
				*curp++ = *in++;
			}
			break;
		case '\'':
			set = 1;
			in++;
			while (*in && *in != '\'') {
				if (*in == '\\' && *(in + 1) != '\0')
					in++;
				*curp++ = *in++;
			}
			if (*in == '\'')
				in++;
			break;
		case '"':
		case '[':
			switch (*in++) {
			case '"':	endc = '"';	break;
			case '[':	endc = ']';	break;
			default:
				fprintf(stderr, "innter error\n");
				return -1;
			}
			set = 1;
			while (*in != endc) {
				switch (*in) {
				case 0:
					fprintf(stderr, "quote not closed\n");
					return -1;
				case '$':
					j = getnamelength(++in);
					if (j == 0) {
						*curp++ = '$';
						break;
					} else if (j >= sizeof(name)) {
						fprintf(stderr, "variable name too long\n");
						return -1;
					}
					memcpy(name, in, j);
					name[j] = '\0';
					in += j;
					if ((p = getvariable(name)) == NULL || *p == '\0') {
						continue;
					}
					if ((curp - argument_split_buffer) + strlen(p) >= sizeof(argument_split_buffer)) {
						fprintf(stderr, "too long\n");
						return -1;
					}
					strcpy(curp, p);
					curp += strlen(p);
					break;
				case '\\':
					switch (*++in) {
					case 0:
						break;
					case 'n':
						*curp++ = '\n';
						in++;
						break;
					case 't':
						*curp++ = '\t';
						in++;
						break;
					default:
						*curp++ = *in++;
						break;
					}
					break;
				default:
					*curp++ = *in++;
					break;
				}
			}
			in++;
			break;
		case '$':
			j = getnamelength(++in);
			if (j == 0) {
				set = 1;
				*curp++ = '$';
				break;
			} else if (j >= sizeof(name)) {
				fprintf(stderr, "variable name too long\n");
				return -1;
			}
			memcpy(name, in, j);
			name[j] = '\0';
			in += j;
			if ((p = getvariable(name)) == NULL) {
				continue;
			}
			while (*p) {
				if (isspace(*p)) {
					p++;
					while (*p && isspace(*p))
						p++;
					if (set) {
						*curp++ = '\0';
						if (++argcnt >= MAXARGS) {
							fprintf(stderr, "too many arguments\n");
							return -1;
						}
						args[argcnt] = curp;
					}
					set = 0;
				} else {
					set = 1;
					*curp++ = *p++;
					if (curp - argument_split_buffer >= sizeof(argument_split_buffer)) {
						fprintf(stderr, "too long\n");
						return -1;
					}
				}
			}
			break;
		default:
			set = 1;
			*curp++ = *in++;
			break;
		}
		if (curp - argument_split_buffer >= sizeof(argument_split_buffer) - 1) {
			fprintf(stderr, "too long\n");
			return -1;
		}
	}
out:
	args[argcnt] = NULL;

	for (j = 1; j < argcnt; j++) {
		setarghist(args[j]);
	}
	return 0;
}

int
if_statement(stmt)
struct statement *stmt;
{
	if (setargs(stmt->line + 3, NULL)) {
		return 0;
	}
	if (getvalue(args[0])) {
		run_commands(stmt->ext1);
	} else {
		run_commands(stmt->ext2);
	}
	return 0;
}

int
while_statement(stmt)
struct statement *stmt;
{
	for (;;) {
		if (setargs(stmt->line + 6, NULL))
			return 0;
		if (!getvalue(args[0]))
			break;
		run_commands(stmt->ext1);
	}
	return 0;
}

PRIVATE int
search_functiontable()
{
	int i;

	for (i = 0; i < functiontable_size; i++) {
		if (strcmp(functiontable[i].name, args[0]) == 0) {
			return i;
		}
	}
	return -1;
}

PRIVATE int
search_commandtable()
{
	int cmd = 0;
	int i;
	size_t len;

	for (i = 1; commandtable[i]; i++) {
		if (strcmp(commandtable[i]->name, args[0]) == 0) {
			cmd = i;
			break;
		}
	}
	if (cmd == 0) {
		len = strlen(args[0]);
		for (i = 1; commandtable[i]; i++) {
			if (strncmp(commandtable[i]->name, args[0], len) == 0) {
				if (cmd) {
					fprintf(stderr, "ambiguous command: %s\n", args[0]);
					lastvalue = 1;
					return -1;
				}
				cmd = i;
			}
		}
		if (cmd == 0) {
			fprintf(stderr, "command not found\n");
			lastvalue = 1;
			return -1;
		}
	}

	if (strlen(commandtable[cmd]->name) + strlen(commandtable[cmd]->args) + 50 > sizeof(usage)) {
		fprintf(stderr, "message table overflow: %s\n", commandtable[cmd]->name);
		lastvalue = 1;
		return -1;
	}
	sprintf(usage, "Error: usage: %s %s", commandtable[cmd]->name, commandtable[cmd]->args);
	return cmd;
}

void
docommand()
{
	int funcno, cmd, i, closeflag;
	pid_t pid;
	addr_t (*func)();
	FILE *savefp;
	int catched = 0;

	optind = 0;
	optarg = NULL;

	if (args[0] == NULL && pipecmd == NULL)
		return;
	if (args[0] == NULL) {
		fflush(stdout);
		TRY {
			sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
			lastvalue = system(pipecmd);
			sigprocmask(SIG_BLOCK, &block_mask, NULL);
		} CATCH {
			sigprocmask(SIG_BLOCK, &block_mask, NULL);
			fprintf(stderr, "%s\n", except_message);
		}
		ENDTRY
		if (catched) {
			THROW("excepted");
		}
		return;
	}
	if (strchr(args[0], '=') && argcnt == 1) {
		setvariable(args[0]);
		return;
	}

	if ((funcno = search_functiontable()) >= 0) {
		func = NULL;
	} else if ((cmd = search_commandtable()) > 0) {
		func = commandtable[cmd]->func;
	} else {
		return;
	}

	savefp = fp;
	closeflag = 0;
	pid = 0;

	if (pipecmd) {
		int fdes[2];
		fflush(NULL);
		if (pipe(fdes) == -1) {
			fprintf(stderr, "pipe: %s\n", strerror(errno));
			lastvalue = 1;
			return;
		}
		fcntl(fdes[0], F_SETFD, 1);
		fcntl(fdes[1], F_SETFD, 1);
		switch (pid = fork()) {
		case -1:
			fprintf(stderr, "fork: %s\n", strerror(errno));
			close(fdes[0]);
			close(fdes[1]);
			lastvalue = 1;
			return;
		case 0:
			dup2(fdes[0], 0);
			if (fileno(fp) != 1)
				dup2(fileno(fp), 1);
			execl("/bin/sh", "sh", "-c", pipecmd, NULL);
			write(2, "cannot execute sh\n", 18);
			_exit(1);
		default:
			close(fdes[0]);
			break;
		}
		if ((fp = fdopen(fdes[1], "w")) == NULL) {
			close(fdes[1]);
			waitpid(pid, NULL, 0);
			fp = savefp;
			lastvalue = 1;
			return;
		}
		closeflag = 1;
	} else if (outfile) {
		if (strchr(outfile, ' ')) {
			fprintf(stderr, "redirect error\n");
			lastvalue = 1;
			return;
		}
		fp = fopen(outfile, "a");
		if (fp == NULL) {
			fp = savefp;
			fprintf(stderr, "%s: %s\n", outfile, strerror(errno));
			lastvalue = 1;
			return;
		}
		fcntl(fileno(fp), F_SETFD, 1);
		fprintf(fp, PS1);
		for (i = 0; i < argcnt; i++) {
			fprintf(fp, "%s ", args[i]);
		}
		fprintf(fp, "\n");
		closeflag = 1;
	}

	if (funcno >= 0) {
		TRY {
			lastvalue = dofunction(funcno);
		} CATCH {
			catched = 1;
		}
		ENDTRY
	} else {
		TRY {
			sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
			lastvalue = func();
			sigprocmask(SIG_BLOCK, &block_mask, NULL);
		} CATCH {
			sigprocmask(SIG_BLOCK, &block_mask, NULL);
			fflush(NULL);
			fprintf(stderr, "%s\n", except_message);
			catched = 1;
		}
		ENDTRY
	}

	if (closeflag) {
		fclose(fp);
	}
	if (pid) {
		waitpid(pid, NULL, 0);
	}
	fp = savefp;

	if (catched) {
		THROW("excepted");
	}
}

void
run_commands(stmt)
struct statement *stmt;
{
	char *next;

	while (stmt) {
		switch (stmt->st_type) {
		case SIMPLE:
			if (setargs(stmt->line, &next) == 0)
				docommand();
			break;
		case IF_CONVENTION:
			if_statement(stmt);
			break;
		case WHILE:
			while_statement(stmt);
			break;
		default:
			abort();
		}
		stmt = stmt->next;
	}
}

PRIVATE void
usage_abort(cmd)
const char *cmd;
{
	fprintf(stderr, "usage: %s [-p /dev/mem] [-q] [mapfile]\n  mapfile: default /boot/System.map\n", cmd);
	exit(1);
}

int
main(argc, argv)
int argc;
char *argv[];
{
	struct statement *stmt;
	int reason;
	int c;
	extern char *optarg;
	extern int optind;

	func_argcnt = 1;
	func_args[0] = argv[0];
	fp = stdout;

	while ((c = getopt(argc, argv, "p:q")) != EOF) {
		switch (c) {
		case 'p':
			physname = optarg;
			break;
		case 'q':
			quiet = 1;
			break;
		default:
			usage_abort(argv[0]);
		}
	}
	if (argc > optind) {
		symname = argv[optind];
		if (argc > optind + 1) {
#ifdef CONF_USE_LKCD
			kerntype = argv[optind + 1];
#else
			usage_abort(argv[0]);
#endif /*CONF_USE_LKCD*/
		}
	}

	TRY {
		init();
	} CATCH {
		fprintf(stderr, "%s\n", except_message);
		exit(1);
	}
	ENDTRY

	/* main loop */
	for (;;) {
		setprompt(PS1);
		setinputfunc(NULL);
		if ((stmt = translate(&reason, NULL)) == NULL) {
			if (reason == G_EOF) {
				break;
			} else {
				continue;
			}
		}
		TRY {
			run_commands(stmt);
		}
		ENDTRY
		freestmt(stmt);
	}
	return 0;
}
