/*
 * EXECOM.C
 *
 * Matthew Dillon, 10 August 1986
 *    Finally re-written.
 *
 * Version 2.07M by Steve Drew 10-Sep-87
 *
 * Version 3.02A by Carlo Borreo & Cesare Dieni 20-Dec-88
 *
 */

extern char *v_histnum, *v_except;

#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_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(), date(), do_protect(), do_ps();
extern int do_forever(), do_abortline(), do_strings(), do_touch();
extern int do_window(), do_search(), do_filenote();
char *push_cpy();

static struct COMMAND Command[] = {
do_run,		0, ST_AV,	0,	"\001",   /* may call do_source */
do_set_var,	0, 0, LEVEL_ALIAS,	"alias",  /* uses avline */
do_abortline,	0, 0,		0,	"abortline",
do_addbuffers,	2, 0,		0,	"addbuffers",
do_aset,	1, 0,		0,	"aset",
do_assign,	0, 0,		0,	"assign",
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",
date,		0, 0,		0,	"date",
do_rm,		0, 0,		0,	"delete",
do_dir,		0, ST_NOEXP,	0,	"dir",
do_diskchange,	1, 0,		0,	"diskchange",
do_inc,		1, 0,		-1,	"dec",
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_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_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_join,	2, 0,		1,	"join",
do_input,	1, 0,		0,	"input",
do_label,	1, ST_COND,	0,	"label",
do_dir,		0, ST_NOEXP,	0,	"ls",
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|ST_NORED,0,	"rpn",
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_touch,	0, 0,		0,	"touch",
do_cat,		0, 0,		0,	"type",
do_unset_var,	0, 0, LEVEL_ALIAS,	"unalias",
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;
   char *istr;
   char *nextstr;
   char *command;
   char *pend_alias = NULL;
   char err = 0;
   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;
   }
/*
   if (str[1] == '$') {
      if (istr = get_var (LEVEL_SET, str + 2))
         str = format_insert_string(str, istr, &freeok);
   }
*/
   istr = NULL;
   if (*(unsigned char *)str < 0x80)
      istr = get_var (LEVEL_ALIAS, str);  /* only if not \command */
   *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;                     /* fix to allow !cmd1;!cmd2 */
      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 == 0) {
      if (cmd_stat(command) & ST_COND)
         goto skipgood;
   }
   if (disable || forward_goto) {
      while (elast && elast != ';' && elast != '|')
         exarg(&nextstr);
      goto done0;
   }
skipgood:
   {
      register char *arg, *ptr, *scr;
      short redir;
      short doexpand;
      short cont;
      short 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, *index();
                  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 arguemnts */
         }
      }
      if (elast == ' ')
         goto step5;
   }
stepdone:
   av[ac] = '\0';

   /* 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;

   {
      register int i;
      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);


      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) {
            i = (*Command[ccno].func)(avline, Command[ccno].val);
            if (i < 0)
               i = 20;
            err = i;
         }
         free (avline);
         if (E_stack == 0 && Lastresult != err) {
            Lastresult = err;
            seterr();
         }
         if ((Command[ccno].stat & (ST_NORED | ST_COND)) == 0) {
            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:
   {
      char *str;
      if (err && E_stack == 0) {
         str = get_var(LEVEL_SET, v_except);
         if (err >= ((str)?atoi(str):1)) {
            if (str) {
               ++H_stack;
               ++E_stack;
               exec_command(str);
               --E_stack;
               --H_stack;
            } else {
               Exec_abortline = 1;
            }
         }
      }
      if (elast != 0 && Exec_abortline == 0)
         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 ((int)err);                  /* TRUE = error occured    */
}


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

   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 int i;
   int len = strlen(str);

   if (*str >= '0'  &&  *str <= '9')
      return (1);
   for (i = 0; Command[i].func; ++i) {
      if (strncmp (str, Command[i].name, len) == 0)
         return (i);
   }
   return (0);
}

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


   for (com = &Command[2]; 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);
}
