/*
 * EXECOM.C
 *
 * Matthew Dillon, 10 August 1986
 *    Finally re-written.
 *
 * Version 2.07M by Steve Drew 10-Sep-87
 *
 * Version 4.01A by Carlo Borreo & Cesare Dieni 17-Feb-90
 *
 */

#define F_EXACT 0
#define F_ABBR  1

#define ST_COND   0x01
#define ST_NORED  0x02
#define ST_NOEXP  0x04
#define ST_AV     0x08 /* delimit args within a variable */

int has_wild = 0;                 /* set if any arg has wild card */

struct COMMAND {
	int (*func)();
	short minargs;
	short stat;
	int val;
	char *name;
	};

extern char *format_insert_string();
extern char *mpush(), *exarg();

extern int do_uniq(), do_man(), do_head(), do_tee();
extern int do_basename(), do_tackon();
extern int do_fltupper(), do_fltlower();
extern int do_strleft(), do_strright(), do_strmid(), do_strlen();
extern int do_fornum(), do_forline(), do_exec();
extern int do_diskchange(), do_stack(), do_fault(), do_path(), do_pri();
extern int do_rpn(), do_resident(), do_truerun(), do_aset(), do_howmany();
extern int do_open(), do_close(), do_fileslist(), do_htype();
extern int do_run(), do_number(), do_assign(), do_join();
extern int do_quit(), do_set_var(), do_unset_var();
extern int do_echo(), do_source(), do_mv(), do_addbuffers();
extern int do_cd(), do_pwd(), do_rm(), do_mkdir(), do_history();
extern int do_mem(), do_cat(), do_dir(), do_info(), do_inc();
extern int do_foreach(), do_return(), do_if(), do_label(), do_goto();
extern int do_input(), do_ver(), do_sleep(), do_help();
extern int do_strhead(), do_strtail(), do_relabel();
extern int do_copy(), do_date(), do_protect(), do_ps();
extern int do_forever(), do_abortline(), do_strings(), do_touch();
extern int do_window(), do_search(), do_filenote(), do_rxrec(), do_rxsend();
char *push_cpy();

static struct COMMAND Command[] = {
do_run,		0, ST_AV,	0,	"\001",   /* may call do_source */
do_abortline,	0, 0,		0,	"abortline",
do_addbuffers,	2, 0,		0,	"addbuffers",
do_set_var,	0, 0, LEVEL_ALIAS,	"alias",  /* uses avline */
do_aset,	1, 0,		0,	"aset",
do_assign,	0, 0,		0,	"assign",
do_basename,	2, 0,		0,	"basename",
do_cat,		0, 0,		0,	"cat",
do_cd,		0, 0,		0,	"cd",
do_close,	0, 0,		0,	"close",
do_copy,	1, 0,		0,	"copy",
do_copy,	1, 0,		0,	"cp",
do_date,	0, 0,		0,	"date",
do_inc,		1, 0,		-1,	"dec",
do_rm,		0, 0,		0,	"delete",
do_dir,		0, ST_NOEXP,	0,	"dir",
do_diskchange,	1, 0,		0,	"diskchange",
do_echo,	0, 0,		0,	"echo", /* uses avline */
do_if,		0, ST_COND,	1,	"else",
do_if,		0, ST_COND,	2,	"endif",
do_exec,	1, 0,		0,	"exec",
do_fault,	1, 0,		0,	"fault",
do_filenote,	2, 0,		0,	"filenote",
do_fileslist,	0, 0,		0,	"flist",
do_fltlower,	0, 0,		0,	"fltlower",
do_fltupper,	0, 0,		0,	"fltupper",
do_foreach,	3, ST_NORED,	0,	"foreach",
do_forever,	1, ST_NORED,	0,	"forever",
do_forline,	3, ST_NORED,	0,	"forline",
do_fornum,	4, ST_NORED,	0,	"fornum",
do_goto,	1, 0,		0,	"goto",
do_head,	1, 0,		0,	"head",
do_help,	0, 0,		0,	"help",
do_history,	0, 0,		0,	"history",
do_howmany,	0, 0,		0,	"howmany",
do_htype,	1, 0,		0,	"htype",
do_if,		1, ST_COND|ST_NORED,0,	"if",
do_inc,		1, 0,		1,	"inc",
do_info,	0, 0,		0,	"info",
do_input,	1, 0,		0,	"input",
do_join,	2, 0,		1,	"join",
do_label,	1, ST_COND,	0,	"label",
do_dir,		0, ST_NOEXP,	0,	"ls",
do_man,		0, 0,		0,	"man",
do_mkdir,	0, 0,		0,	"md",
do_mem,		0, 0,		0,	"mem",
do_mkdir,	0, 0,		0,	"mkdir",
do_mv,		2, 0,		0,	"mv",
do_open,	3, 0,		0,	"open",
do_path,	0, 0,		0,	"path",
do_pri,		2, 0,		0,	"pri",
do_protect,	2, 0,		0,	"protect",
do_ps,		0, 0,		0,	"ps",
do_pwd,		0, 0,		0,	"pwd",
do_quit,	0, ST_NORED,	0,	"quit",
do_truerun,	1, ST_NORED,	1,	"rback",
do_mv,		2, 0,		0,	"rename",
do_relabel,	2, 0,		0,	"relabel",
do_resident,	0, 0,		0,	"resident",
do_return,	0, 0,		0,	"return",
do_rm,		0, 0,		0,	"rm",
do_rpn,		0, ST_NOEXP,	0,	"rpn",
do_rxrec,	0, 0,		0,	"rxrec",
do_rxsend,	2, 0,		0,	"rxsend",
do_truerun,	1, ST_NORED,	0,	"run",
do_search,	2, 0,		0,	"search",
do_set_var,	0, ST_AV, LEVEL_SET,	"set",
do_sleep,	0, 0,		0,	"sleep",
do_source,	0, ST_NORED|ST_AV, 0,	"source", /* uses avline */
do_stack,	0, 0,		0,	"stack",
do_strhead,	3, 0,		0,	"strhead",
do_strings,	1, 0,		0,	"strings",
do_strleft,	3, 0,		0,	"strleft",
do_strlen,	2, 0,		0,	"strlen",
do_strmid,	3, 0,		0,	"strmid",
do_strright,	3, 0,		0,	"strright",
do_strtail,	3, 0,		0,	"strtail",
do_tackon,	3, 0,		0,	"tackon",
do_head,	1, 0,		1,	"tail",
do_tee,		0, 0,		0,	"tee",
do_touch,	0, 0,		0,	"touch",
do_cat,		0, 0,		0,	"type",
do_unset_var,	0, 0, LEVEL_ALIAS,	"unalias",
do_uniq,	0, 0,		0,	"uniq",
do_unset_var,	0, 0, LEVEL_SET  ,	"unset",
do_ver,		0, 0,		0,	"version",
do_window,	0, ST_NOEXP,	0,	"window",
'\0',		0, 0,		0,	NULL
};

static unsigned char elast;		/* last end delimeter */
static char Cin_ispipe, Cout_ispipe;

exec_command(base)
char *base;
{
register char *scr;
char buf[32];

if (!H_stack) {
	add_history(base);
	sprintf(buf, "%d", H_tail_base + H_len);
	set_var(LEVEL_SET, v_histnum, buf);
	}
scr = malloc((strlen(base) << 2) + 2);
preformat(base, scr);
return (fcomm(scr, 1) ? -1 : 1);
}

isalphanum(c)
register char c;
{
return (
	(c >= '0' && c <= '9') ||
	(c >= 'a' && c <= 'z') ||
	(c >= 'A' && c <= 'Z') ||
	(c == '_')
	);
}

preformat(s, d)
register char *s, *d;
{
register int si, di, qm;

si = di = qm = 0;
while (s[si] == ' ' || s[si] == 9) ++si;
while (s[si]) {
	if (qm && s[si] != '\"' && s[si] != '\\') {
		d[di++] = s[si++] | 0x80;
		continue;
		}
	switch (s[si]) {
		case ' ':
		case 9:
			d[di++] = ' ';
			while (s[si] == ' ' || s[si] == 9) ++si;
			if (s[si] == 0 || s[si] == '|' || s[si] == ';') --di;
			break;
		case '*':
		case '?':
			d[di++] = 0x80;
		case '!':
			d[di++] = s[si++];
			break;
		case '#':
			d[di++] = '\0';
			while (s[si]) ++si;
			break;
		case ';':
		case '|':
			d[di++] = s[si++];
			while (s[si] == ' ' || s[si] == 9) ++si;
			break;
		case '\\':
			d[di++] = s[++si] | 0x80;
			if (s[si]) ++si;
			break;
		case '\"':
			qm = 1 - qm;
			++si;
			break;
		case '^':
			d[di++] = s[++si] & 0x1F;
			if (s[si]) ++si;
			break;
		case '$': /* search end of var name and place false space */
			d[di++] = 0x80;
			d[di++] = s[si++];
			while (isalphanum(s[si])) d[di++] = s[si++];
			d[di++] = 0x80;
			break;
		default:
			d[di++] = s[si++];
			break;
		}
	}
d[di++]=0;
d[di]=0;
if (debug) fprintf (stderr,"PREFORMAT: %d :%s:\n", strlen(d), d);
}

extern BPTR extOpen();

/*
 * process formatted string.  ' ' is the delimeter.
 *
 *    0: check '\0': no more, stop, done.
 *    1: check $.     if so, extract, format, insert
 *    2: check alias. if so, extract, format, insert. goto 1
 *    3: check history or substitution, extract, format, insert. goto 1
 *
 *    4: assume first element now internal or disk based command.
 *
 *    5: extract each ' ' or 0x80 delimited argument and process, placing
 *       in av[] list (except 0x80 args appended).  check in order:
 *
 *             '$'         insert string straight
 *             '>'         setup stdout
 *             '>>'        setup stdout flag for append
 *             '<'         setup stdin
 *             '*' or '?'  do directory search and insert as separate args.
 *
 *             ';' 0 '|'   end of command.  if '|' setup stdout
 *                          -execute command, fix stdin and out (|) sets
 *                           up stdin for next guy.
 */

fcomm(str, freeok)
register char *str;
{
static int alias_count;
int p_alias_count=0, err=0;
char *istr, *nextstr, *command, *pend_alias=NULL, *temp;

has_wild = 0;
++alias_count;
mpush_base();
if (*str == 0) goto done1;

step1:

if (alias_count == MAXALIAS || ++p_alias_count == MAXALIAS) {
	fprintf(stderr,"Alias Loop\n");
	err = 20;
	goto done1;
	}
istr = NULL;
if (*str >= 0) istr=get_var(LEVEL_ALIAS, str);
	/* only if not \command, i.e. bit 7 not set */
*str &= 0x7F;
	/* remove \ teltail */

if (istr) {
	if (*istr == '%') pend_alias = istr;
	else {
		str = format_insert_string(str, istr, &freeok);
		goto step1;
		}
	}

if (*str == '!') {
	char *p, c;

	for (p = str; *p && *p != ';' ; ++p);
	c = *p;
	*p = '\0';
	istr = get_history(str);
	*p = c;
	replace_head(istr);
	str = format_insert_string(str, istr, &freeok);
	goto step1;
	}

nextstr = str;
command = exarg(&nextstr);
if (*command == 0) goto done0;
if (
	(pend_alias || ! (cmd_stat(command) & ST_COND) ) &&
	(disable || forward_goto)
	) {
		while (elast && elast != ';' && elast != '|') exarg(&nextstr);
		goto done0;
	}

{
register char *arg, *ptr, *scr;
short redir, doexpand, cont, inc;

ac = 1;
av[0] = command;

step5:                                          /* ac = nextac */

if (!elast || elast == ';' || elast == '|') goto stepdone;

av[ac] = '\0';
cont = 1;
doexpand = redir = inc = 0;

while (cont && elast) {
   int cstat = cmd_stat(command);

   ptr = exarg(&nextstr);
   inc = 1;
   arg = "";
   cont = (elast == 0x80);
   switch (*ptr) {
   case '<':
      redir = -2;
   case '>':
      if (cstat & (ST_NORED | ST_COND)) {
                                                  /* don't extract   */
          redir = 0;                              /* <> stuff if its */
          arg = ptr;                              /* external cmd.   */
          break;
      }
      ++redir;
      arg = ptr + 1;
      if (*arg == '>') {
         redir = 2;        /* append >> */
         ++arg;
      }
      cont = 1;
      break;
   case '$':
      /* restore args if from set command or pend_alias */
      if ((arg = get_var(LEVEL_SET, ptr + 1)) != NULL) {
         if (cstat & ST_COND) {
            char *tp;
            tp = push_cpy(arg);
            arg = tp;
         }
         else {
            char *pe, sv;
            while (pe = index(arg,0xA0)) {
               sv = *pe;
               *pe = '\0';
               av[ac++] = push_cpy(arg);
               *pe = sv;
               av[ac] = '\0';
               arg = pe+1;
            }
         }
      }
      else arg = ptr;
      break;
   case '*':
   case '?':
      if ((cstat & ST_NOEXP) == 0) doexpand = 1;
      arg = ptr;
      break;
   default:
      arg = ptr;
      break;
   }

   /* Append arg to av[ac] */

   for (scr = arg; *scr; ++scr) *scr &= 0x7F;
   if (av[ac]) {
      register char *old = av[ac];
      av[ac] = mpush(strlen(arg)+strlen(av[ac]));
      strcpy(av[ac], old);
      strcat(av[ac], arg);
   } else av[ac] = push_cpy(arg);
   if (elast != 0x80) break;
}

/* process expansion */

if (doexpand) {
	char **eav, **ebase;
	int eac;

	has_wild = 1;
	eav = ebase = expand(av[ac], &eac);
	inc = 0;
	if (eav) {
		if (ac + eac + 2 > MAXAV) {
			ierror (NULL, 506);
			err = 1;
			}
		else {
			QuickSort(eav, eac);
			for (; eac; --eac, ++eav) av[ac++] = push_cpy(*eav);
			}
		free_expand (ebase);
		}
	}

/* process redirection  */

if (redir && !err) {
	register char *file = doexpand ? av[--ac] : av[ac];

	if (redir < 0) Cin_name = file;
	else {
		Cout_name = file;
		Cout_append = (redir == 2);
		}
	inc = 0;
	}

/* check elast for space */

if (inc) {
	++ac;
	if (ac + 2 > MAXAV) {
		ierror(NULL, 506);
		err = 1;	/* error condition */
		elast = 0;	/* don't process any more args */
		}
	}
if (elast == ' ') goto step5;
}

stepdone:

av[ac] = NULL;

/* process pipes via files */

if (elast == '|' && !err) {
	static int which;	/* 0 or 1 in case of multiple pipes */

	which = 1 - which;
	Cout_name = (which ? Pipe1 : Pipe2);
	Cout_ispipe = 1;
	}

if (err) goto done0;

{
char save_elast;
char *compile_av();
register char *avline;
unsigned char delim = ' ';

save_elast = elast;
if (pend_alias || (cmd_stat(command) & ST_AV)) delim = 0xA0;
avline = compile_av(av,((pend_alias) ? 1 : 0), ac, delim, 0);

if (pend_alias) {	/* special % alias */
	register char *ptr, *scr;

	for (ptr = pend_alias; *ptr && *ptr != ' '; ++ptr);
	set_var (LEVEL_SET, pend_alias + 1, avline);
	free (avline);

	scr = malloc((strlen(ptr) << 2) + 2);
	preformat (ptr, scr);
	fcomm (scr, 1);
	unset_var (LEVEL_SET, pend_alias + 1);
	}
else {	/* normal command  */
	register int ccno;
	long oldcin  = Myprocess->pr_CIS;
	long oldcout = Myprocess->pr_COS;
	char *Cin_buf;
	struct FileHandle *ci;
	long oldbuf;

	fflush(stdout);
	ccno = find_command (command);
	if ((Command[ccno].stat & (ST_NORED | ST_COND)) == 0) {
		if (Cin_name) {
			if ((Cin = (long)extOpen(Cin_name,1005L)) == 0L) {
				ierror (NULL, 504);
				err = 1;
				Cin_name = '\0';
				}
			else {
				Myprocess->pr_CIS = _devtab[stdin->_unit].fd = Cin;
				ci = (struct FileHandle *)(((long)Cin)<<2);
				Cin_buf = (char *)AllocMem(202L, MEMF_PUBLIC);
				oldbuf = ci->fh_Buf;
				if (ci->fh_Buf == 0) /* fexec expects a CIS buffer */
					ci->fh_Buf = (long)Cin_buf>>2;
				}
			}
		if (Cout_name) {
			if (Cout_append && (Cout =(long)extOpen(Cout_name, 1005L)) )
				Seek(Cout, 0L, 1L);
			else
				Cout = (long)extOpen(Cout_name,1006L);
			if (Cout == NULL) {
				err = 1;
				ierror (NULL, 504);
				Cout_name = '\0';
				Cout_append = 0;
				}
			else Myprocess->pr_COS = _devtab[stdout->_unit].fd = Cout;
			}
		}
	if (ac < Command[ccno].minargs + 1) {
		ierror (NULL, 500);
		err = -1;
		}
	else if (!err) {
		err = (*Command[ccno].func)(avline, Command[ccno].val);
		if (err<0) err=20;
		}
	free (avline);
	if (E_stack == 0 && Lastresult != err) {
		Lastresult = err;
		seterr();
		}
	if ( ! (Command[ccno].stat & (ST_NORED | ST_COND))) {
		if (Cin_name) {
			fflush(stdin);
			clearerr(stdin);
			ci->fh_Buf = oldbuf;
			extClose(Cin);
			FreeMem(Cin_buf, 202L);
			}
		if (Cout_name) {
			fflush(stdout);
			clearerr(stdout);
			stdout->_flags &= ~_DIRTY;	 /* because of nil: device */
			extClose(Cout);
			Cout_append = 0;
			}
		}
	Myprocess->pr_CIS =  _devtab[stdin->_unit].fd  = oldcin;
	Myprocess->pr_COS =  _devtab[stdout->_unit].fd = oldcout;
	}

if (Cin_ispipe && Cin_name) DeleteFile(Cin_name);
if (Cout_ispipe) {
	Cin_name = Cout_name;	/* ok to assign.. static name */
	Cin_ispipe = 1;
	}
else Cin_name = '\0';
Cout_name = '\0';
Cout_ispipe = 0;
elast = save_elast;
}

mpop_tobase();	/* free arguments   */
mpush_base();	/* push dummy base  */

done0:

if (err && E_stack == 0) {
	temp = get_var(LEVEL_SET, v_except);
	if (err >= (temp ? atoi(temp) : 1)) {
		if (temp) {
			++H_stack;
			++E_stack;
			exec_command(temp);
			--E_stack;
			--H_stack;
			}
		else Exec_abortline = 1;
		}
	}
if (elast && ! Exec_abortline) err = fcomm(nextstr, 0);
Exec_abortline = 0;
if (Cin_name) DeleteFile(Cin_name);
Cin_name = NULL;
Cin_ispipe = 0;

done1:

mpop_tobase();
if (freeok) free(str);
--alias_count;
return err;	/* TRUE = error occured */
}


char *exarg(ptr)
unsigned char **ptr;
{
register unsigned char *start, *end;

start = end = *ptr;
while(*end && *end!=0x80 && *end!=';' && *end!='|' && *end!=' ') ++end;
elast = *end;
*end = '\0';
*ptr = end + 1;
return (char *)start;
}

static char **Mlist;

mpush_base()
{
char *str;

str = malloc(5);
*(char ***)str = Mlist;
str[4] = 0;
Mlist = (char **)str;
}

char *mpush(bytes)
{
char *str;

str = malloc(6 + bytes + 2); /* may need extra 2 bytes in do_run() */
*(char ***)str = Mlist;
str[4] = 1;
Mlist = (char **)str;
return (str + 5);
}

mpop_tobase()
{
register char *next;

while (Mlist) {
	next = *Mlist;
	if (((char *)Mlist)[4] == 0) {
		free (Mlist);
		Mlist = (char **)next;
		break;
	}
	free (Mlist);
	Mlist = (char **)next;
	}
}

/*
 * Insert 'from' string in front of 'str' while deleting the
 * first entry in 'str'.  if freeok is set, then 'str' will be
 * free'd
 */

char *format_insert_string(str, from, freeok)
char *str;
char *from;
int *freeok;
{
register char *new1, *new2;
register unsigned char *strskip;
int len;

for (strskip = (unsigned char *)str;
		*strskip && *strskip != ' ' 
		&& *strskip != ';' && *strskip != '|'
		&& *strskip != 0x80; ++strskip);
len = strlen(from);
new1 = malloc((len << 2) + 2);
preformat(from, new1);
len = strlen(new1) + strlen(strskip);
new2 = malloc(len+2);
strcpy(new2, new1);
strcat(new2, strskip);
new2[len+1] = 0;
free (new1);
if (*freeok) free (str);
*freeok = 1;
return new2;
}

cmd_stat(str)
char *str;
{
return(Command[find_command(str)].stat);
}

find_command(str)
char *str;
{
register unsigned short i;
int len = strlen(str);

if (*str<'a' || *str>'z') return 0;
for (i = 0; Command[i].func; ++i)
	if ( ! strncmp(str, Command[i].name, len)) return (int)i;
return 0;
}

do_help()
{
register struct COMMAND *com;
int i=0;

for (com = &Command[1]; com->func; ++com) {
	printf ("%-12s", com->name);
	if (++i % 6 == 0) printf("\n");
	}
printf("\n");
return 0;
}

char *push_cpy(s)
char *s;
{
return strcpy(mpush(strlen(s)), s);
}
