Logo Search packages:      
Sourcecode: zmailer version File versions  Download package

interpret.c

/*
 *    Copyright 1989 by Rayan S. Zachariassen, all rights reserved.
 *    This will be free software, but only when it is finished.
 */

/*
 * Runtime interpreter for the shell pseudo-code.
 */

#include "hostenv.h"
#include "listutils.h"
#ifdef      MAILER
#include "sift.h" /* Include this BEFORE "mailer.h" ! */
#endif      /* MAILER */

#include "mailer.h"
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/file.h>

#include "zsyslog.h"

/* #define static
   #define register */
#define RUNIO(X)  (/*fprintf(runiofp, "runio(%s,%d)\n", __FILE__, __LINE__), */runio(&X))

#include "interpret.h"
#include "io.h"
#include "shconfig.h"
#include "libc.h"
#include "libz.h"
#include "libsh.h"

extern struct sptree *spt_funclist;
extern int wait __((int *));
extern int v_record, v_changed;

int magic_number = 3;   /* check id for precompiled script files */
long bin_magic   = 1;   /* Another check-id -- exec file  st_ctime ? */

#if 0
#undef STATIC
#define STATIC /**/
#endif

STATIC int pipefd;            /* read side fd of a pipe, or -1 */

STATIC struct osCmd *ib_command[20];      /* max # nested $() in stack */
STATIC int ibt = -1;          /* top of ib_command stack, XXX reset somewh. */
STATIC const char *uBLANK = "";
/*
 * Because the structure of the interpreter is to build up a command descriptor
 * and then execute it, and the shell must remain relatively intact I/O-wise,
 * we have to predetermine which I/O actions should be taken to set up the
 * environment for a particular program or command.  To do this, we have to
 * know the effects of I/O-related system calls on the environment.  The only
 * possible effect is to create or destroy (or leave alone) one or more
 * file descriptors.  The algorithms used by the kernel to default file
 * descriptors (e.g. in an open() call) are well specified, so we can simulate
 * all that activity a-priori.  This in turn is necessary in order to know
 * how to undo these actions.  The simulation is aided by an array we maintain
 * of busy (i.e. in-use) file descriptors.
 */

STATIC short      fds[MAXNFILE];                /* could be a bitmap */
#define     FDBUSY(X)   ((X) > (sizeof fds / sizeof fds[0]) ? abort(),0 : fds[X])

/*
 * This routine is used to model the effects of I/O operations on the
 * set of file descriptors.
 */

STATIC void freeio __((struct IOop *, int));
STATIC void
freeio(fioop, mark)
      register struct IOop *fioop;
      int mark;
{
      struct IOop *nfioop;
      int fd;

      for (; fioop != NULL; fioop = nfioop) {
            nfioop = fioop->next;
            if (1) {
                  if (fioop->cmd == sIOdup)
                        fd = fioop->fd2;
                  else
                        fd = fioop->fd;
                  fds[fd] = (fioop->cmd != sIOclose);

                  if (isset('R'))
                        fprintf(stderr,
                              "fds[%d] = %d\n",
                              fd, (fioop->cmd != sIOclose));
            }
            if (fioop->opflags)
                  free((void *)fioop);
      }
}

#define NO_DEQUOTE_AT_SIFTS

/* #define DEQUOTE_STICKY */

STATIC char *dequote __((const char *str, int len));
STATIC char *
dequote (str, len)
      const char *str;
      int   len;
{
#ifndef NO_DEQUOTE_AT_SIFTS
      const char *sp, *ep;
      char *s;
#endif
      char *s0;

#ifdef DEQUOTE_STICKY
      memtypes stickytmp = stickymem;
      stickymem = MEM_SHCMD;

/* fprintf(stderr,"dequote(\"%s\",%d) => ",str,len); */

      s0 = tmalloc(len+1); /* All subsequent runs will be smaller,
                        AND they fit running in-place! */
#else
      s0 = emalloc(len+1); /* All subsequent runs will be smaller,
                        AND they fit running in-place! */
#endif

#ifdef NO_DEQUOTE_AT_SIFTS
      memcpy(s0, str, len);
      s0[len] = 0;
#else
      do {

        sp = str;
        ep = str + len -1;

        if (len > 1 && *sp == *ep && 
            (*sp == '"' || *sp == '\'')) {
          ++sp;
          --ep;
          len -= 2;

          for (s = s0; len > 0; ++sp, --len, ++s) {
            if (*sp == '\\' && sp[1] != 0) {
            *s = *++sp;
            --len;
            } else
            *s = *sp;
          }
          *s = 0;
        } else {
          if (s0 != sp) /* Don't copy, if 'in-place' */
            memcpy(s0, sp, len);
          s = s0;
          if (len > 0)
            s += len;
          *s = 0;
        }

        ep = s-1;

        str = s0;
        len = strlen(str);

      } while (ep > s0 && *s0 == *ep && (*s0 == '"' || *s0 == '\''));
#endif
/* fprintf(stderr,"\"%s\"\n",s0); */

#ifdef DEQUOTE_STICKY
      stickymem = stickytmp;
#endif

      return (s0);
}

extern void free_tregexp __((tregexp *prog));

STATIC void free_regexp __((regexp *prog));
STATIC void
free_regexp (prog)
      regexp *prog;
{
      if (prog != NULL) {
            regfree(&prog->re);
            free((void*)(prog->pattern));
            free(prog);
      }
}

STATIC const char * regsub __((regexp *, int));
STATIC const char *
regsub(prog, n)
      regexp *prog;
      int n;
{
      if (prog == NULL || n < 0 || n > prog->re.re_nsub)
            return (NULL);

      return (prog->match[n]);
}

STATIC regexp *reg_comp __((const char *str, int slen));
STATIC regexp *
reg_comp (str, slen)
      const char  *str;
      int          slen;
{
      const char *reg_stat;
      regexp     *prog;
      char *s;

      prog = (regexp *) malloc(sizeof(regexp));
      if (prog == NULL) {
            fprintf(stderr, "%s: regexp %s: No space\n",
                  progname, str);
            return (NULL);
      }
      memset((void*)prog, 0, sizeof(regexp));

      prog->pattern = s = emalloc(slen+1);
      memcpy(s, str, slen+1);
      s[slen] = 0; /* Just in case */

      reg_stat = re_compile_pattern(prog->pattern, slen, &prog->re);
      if (reg_stat != NULL) {
            fprintf(stderr,"%s: regexp %s: %s\n",progname,str,reg_stat);
            free((void*)(prog->pattern));
            free(prog);
            return (NULL);
      }

      return (prog);
}

STATIC int reg_exec __((regexp *, const char *));
STATIC int
reg_exec (prog, str)
      regexp      *prog;
      const char *str;
{
      int         i;
      int         re_stat;
      regmatch_t  *pmatch;

      if (prog == NULL) {
            fprintf(stderr, "%s: regexp: NULL program\n", progname);
            return (0);
      }

#define DEBUG /* allow traceing here as happens with tregexp.c ! */
#ifdef  DEBUG
      if (D_compare) {
            fprintf(stderr,
                  "%*sscomparing '%s' and ", 4*funclevel, " ", prog->pattern);
            if (str != NULL)
                  fprintf(stderr, "'%s'\n", str);
            else
                  fprintf(stderr, "(nil)\n");
      }
#endif  /* DEBUG */

#ifndef USE_ALLOCA
      pmatch = (regmatch_t *)
                  emalloc((prog->re.re_nsub+1)*sizeof(regmatch_t));
#else
      pmatch = (regmatch_t *)
                  alloca((prog->re.re_nsub+1)*sizeof(regmatch_t));
#endif

      re_stat = regexec(&prog->re, str, prog->re.re_nsub+1, pmatch, 0);
      if (re_stat == REG_NOMATCH) {
#ifndef USE_ALLOCA
            free(pmatch);
#endif
            return 0;
      }

      for (i=0; i<=prog->re.re_nsub; i++)
        prog->match[i] = strnsave(str + pmatch[i].rm_so,
                            pmatch[i].rm_eo - pmatch[i].rm_so);

#ifdef  DEBUG
      if (D_matched) {
            fprintf(stderr,
                  "%*ssmatched '%s' and ", 4*funclevel, " ", prog->pattern);
            if (str != NULL)
                  fprintf(stderr, "'%s'\n", str);
            else
                  fprintf(stderr, "(nil)\n");
      }
#endif  /* DEBUG */

#ifndef USE_ALLOCA
      free(pmatch);
#endif

      return 1;
}


/*
 * Return the next available filedescriptor, simulating kernel lookup.
 */

STATIC int findfreefd __((void));
STATIC int
findfreefd()
{
        register int fd;

        for (fd = 0; fd <= (sizeof fds / sizeof fds[0]) ; ++fd)
                if (fds[fd] == 0) {
#ifdef  MAILER
                        /*
                         * This is supposed to compensate for random
                         * fopen's in the application, e.g. when caching
                         * file descriptors keeping a database open.
                         * This is a bit too expensive for my liking,
                         * and assumes these things are static as compared
                         * to shell code execution, so beware of subtle bugs.
                         */
                        if (fcntl(fd, F_GETFL,0) >= 0)
                                continue;
#endif  /* MAILER */
                        return fd;
                }
        fprintf(stderr, "%s: out of free filedescriptors (%d)!\n",
                        progname, fd);
        abort(); /* Out of free file-descriptors! */
        /* NOTREACHED */
      return 0;
}


#ifdef      MAILER

/*
 * The mailer should call this routine before toplevel entry to the
 * shell, and then be *VERY* careful about opening files in routines
 * that may be called from within shell execution or between apply calls.
 */

int
setfreefd()
{
      register int i;

#if 0
      register int fd;
      struct stat stbuf;
      
      for (fd = 0; fd <= (sizeof fds / sizeof fds[0]) ; ++fd)
            if (fstat(fd, &stbuf) == 0)
                  fds[i = fd] = 1;
            else
                  fds[fd] = 0;
#else
      i = -1;
#endif
      return i;
}
#endif      /* MAILER */

STATIC const char * ename __((OutputTokens));
STATIC const char *
ename(cmd)
      OutputTokens cmd;
{
      const char *s = NULL;

      switch (cmd) {
      case sBufferSet:  s = "sBufferSet"; break;
      case sBufferAppend:     s = "sBufferAppend"; break;
      case sBufferExpand:     s = "sBufferExpand"; break;
      case sBufferQuote:      s = "sBufferQuote"; break;
      case sBufferSetFromArgV: s = "sBufferSetFromArgV"; break;
      case sArgVpush:         s = "sArgVpush"; break;
      case sArgList:          s = "sArgList"; break;
      case sVariablePush:     s = "sVariablePush"; break;
      case sVariablePop:      s = "sVariablePop"; break;
      case sVariableCdr:      s = "sVariableCdr"; break;
      case sVariableBuffer:   s = "sVariableBuffer"; break;
      case sVariableAppend:   s = "sVariableAppend"; break;
      case sVariableLoopAttach: s = "sVariableLoopAttach"; break;
      case sCommandPush:      s = "sCommandPush"; break;
      case sCommandPop: s = "sCommandPop"; break;
      case sCommandCarryBuffer: s = "sCommandCarryBuffer"; break;
      case sIOopen:           s = "sIOopen"; break;
      case sIOopenString:     s = "sIOopenString"; break;
      case sIOopenPortal:     s = "sIOopenPortal"; break;
      case sIOopenPipe: s = "sIOopenPipe"; break;
      case sIOintoBuffer:     s = "sIOintoBuffer"; break;
      case sIOclose:          s = "sIOclose"; break;
      case sIOdup:            s = "sIOdup"; break;
      case sIOsetIn:          s = "sIOsetIn"; break;
      case sIOsetInOut: s = "sIOsetInOut"; break;
      case sIOsetOut:         s = "sIOsetOut"; break;
      case sIOsetAppend:      s = "sIOsetAppend"; break;
      case sIOsetDesc:  s = "sIOsetDesc"; break;
      case sIObufIn:          s = "sIObufIn"; break;
      case sIObufOut:         s = "sIObufOut"; break;
      case sIObufFree:  s = "sIObufFree"; break;
      case sIObufString:      s = "sIObufString"; break;
      case sAssign:           s = "sAssign"; break;
      case sAssignTemporary:  s = "sAssignTemporary"; break;
      case sFunction:         s = "sFunction"; break;
      case sParameter:  s = "sParameter"; break;
      case sJump:       s = "sJump"; break;
      case sBranchOrigin:     s = "sBranchOrigin"; break;
      case sJumpFork:         s = "sJumpFork"; break;
      case sJumpIfFailure:    s = "sJumpIfFailure"; break;
      case sJumpIfSuccess:    s = "sJumpIfSuccess"; break;
      case sJumpIfNilVariable: s = "sJumpIfNilVariable"; break;
      case sJumpIfMatch:      s = "sJumpIfMatch"; break;
      case sJumpIfFindVarNil: s = "sJumpIfFindVarNil"; break;
      case sJumpIfOrValueNil: s = "sJumpIfOrValueNil"; break;
      case sJumpLoopBreak:    s = "sJumpLoopBreak"; break;
      case sJumpLoopContinue: s = "sJumpLoopContinue"; break;
      case sLoopEnter:  s = "sLoopEnter"; break;
      case sLoopExit:         s = "sLoopExit"; break;
      case sLocalVariable:    s = "sLocalVariable"; break;
      case sScopePush:  s = "sScopePush"; break;
      case sScopePop:         s = "sScopePop"; break;
      case sDollarExpand:     s = "sDollarExpand"; break;
      case sNoOp:       s = "sNoOp"; break;
      case sPrintAndExit:     s = "sPrintAndExit"; break;
      case sBackground: s = "sBackground"; break;
      case sSiftPush:         s = "sSiftPush"; break;
      case sSiftBody:         s = "sSiftBody"; break;
      case sSiftCompileRegexp: s = "sSiftCompileRegexp"; break;
      case sSiftReevaluate:   s = "sSiftReevaluate"; break;
      case sSiftPop:          s = "sSiftPop"; break;
      case sSiftBufferAppend: s = "sSiftBufferAppend"; break;
      case sJumpIfRegmatch:   s = "sJumpIfRegmatch"; break;
      case sTSiftPush:  s = "sTSiftPush"; break;
      case sTSiftBody:  s = "sTSiftBody"; break;
      case sTSiftCompileRegexp: s = "sTSiftCompileRegexp"; break;
      case sTSiftReevaluate:  s = "sTSiftReevaluate"; break;
      case sTSiftPop:         s = "sTSiftPop"; break;
      /* case sTSiftBufferAppend: s = "sTSiftBufferAppend"; break; */
      case sTJumpIfRegmatch:  s = "sTJumpIfRegmatch"; break;
      default: break;
      }
      return s;
}

/*
 * Auxiliary routine (referenced by the INSERTIO macro) which links an
 * I/O action into a linked list of same (in reverse order of execution).
 * Since two of the parameters to the insertio() function are always the
 * same, and the list of arguments so long, we also define a macro invocation
 * for this function.
 */

#define     INSERTIO(X,C,Y,FD1,FD2,PERM)  insertio((X),(C),name,(Y),ioflags,(FD1),(FD2),(PERM))

STATIC struct IOop * insertio __((struct IOop **, struct osCmd *,
                          const char *, OutputTokens,
                          int, int, int, int));
STATIC struct IOop *
insertio(ioopp, command, name, cmd, ioflags, fd, fd2, opflags)
      struct IOop **ioopp;
      struct osCmd *command;
      const char *name;
      OutputTokens cmd;
      int ioflags, fd, fd2, opflags;
{
      register struct IOop *iotmp;

      if (isset('R'))
            fprintf(stderr,
                  "insertio(%p): %s %d %d\n",ioopp, ename(cmd), fd, fd2);
      if (opflags)
            iotmp = (struct IOop *)emalloc(sizeof (struct IOop));
      else
            iotmp = (struct IOop *)tmalloc(sizeof (struct IOop));
      iotmp->command = command;
#if 1
      iotmp->name = name;
#else
      if (name == NULL)
            iotmp->name = NULL;
      else
            iotmp->name = strsave(name);
#endif
      iotmp->cmd = cmd;
      iotmp->ioflags = ioflags;
      iotmp->opflags = opflags;
      iotmp->fd = fd;
      iotmp->fd2 = fd2;
      if (ioopp != NULL) {
            iotmp->next = *ioopp;
            *ioopp = iotmp;
      } else
            iotmp->next = NULL;
      return iotmp;
}

/*
 * When actions are executed in a particular order A,B,C, they sometimes
 * need to be undone in the reverse order: undo C, undo B, undo A.  To
 * aid that, the APPENDIO() macro will do the obvious thing, in contrast
 * with INSERTIO().  Due to the io op list reversal in runio, the APPENDIO's
 * have to be done in reverse natural order.  See below.
 */

#define     APPENDIO(X,C,Y,FD1,FD2) appendio((X),(C),name,(Y),ioflags,(FD1),(FD2))

STATIC void appendio __((struct IOop **, struct osCmd *, const char *,
                   OutputTokens, int, int, int));
STATIC void
appendio(ioopp, command, name, cmd, ioflags, fd, fd2)
      struct IOop **ioopp;
      struct osCmd *command;
      const char *name;
      OutputTokens cmd;
      int ioflags, fd, fd2;
{
      register struct IOop *iotmp;

      if (isset('R'))
            fprintf(stderr,
                  "appendio(%x): %s %d %d\n", ioopp, ename(cmd), fd, fd2);
      iotmp = *ioopp;
      if (iotmp != NULL) {
            for (; iotmp->next != NULL; iotmp = iotmp->next)
                  continue;
            iotmp->next = insertio((struct IOop **)NULL, command, name,
                               cmd, ioflags, fd, fd2, 0);
      } else
            *ioopp = insertio((struct IOop **)NULL, command, name,
                               cmd, ioflags, fd, fd2, 0);
}


/*
 * This routine is the basic component for building up I/O descriptor
 * manipulation action lists (what a mouthful) that are later carried
 * out by RUNIO().  It models the effects of I/O system calls on the
 * set of available filedescriptors.
 */

STATIC void ioop __((OutputTokens, struct osCmd *, const char *,
                 int, int, const char *));
STATIC void
ioop(cmd, command, name, ioflags, defaultfd, arg1)
      OutputTokens cmd;
      struct osCmd *command;
      const char *name, *arg1;
      int ioflags, defaultfd;
{
      int   tofd, savefd, intobufflag;

      if (isset('R'))
            fprintf(stderr, "ioop(%x): %s\n", command, ename(cmd));
      intobufflag = 0;
      tofd = defaultfd;
      if (cmd == sIOdup) {
            defaultfd = atoi(arg1);
            if (!FDBUSY(defaultfd)) {
                  fprintf(stderr, "%s: no fd %d!\n",
                        progname, defaultfd);
                  return;
            }
      }
      if (FDBUSY(tofd)) {
            /* save current to-fd somewhere else: save-fd */
            savefd = findfreefd();
            INSERTIO(&command->doio, command, sIOdup, tofd, savefd, 0);
            fds[savefd] = 1;
      } else
            savefd = 0; /* shut up the compiler */
      if (cmd == sIOopenPipe && !(ioflags & O_CREAT)) {
            if ((defaultfd = pipefd) < 0) {
                  fprintf(stderr, "%s: no pipe!\n", progname);
                  abort(); /* No pipe on IOopenPipe when it should exist! */
            }
      } else {
            if (cmd == sIOintoBuffer) {
                  cmd = sIOopenPipe;
                  command->iocmd = ioIntoBuffer;
                  intobufflag = 1;
            } else if (cmd == sIOopenPipe)
                  command->iocmd = ioOpenPipe;
            INSERTIO(&command->doio, command, cmd, defaultfd, tofd, 0);
            defaultfd = findfreefd();
      }
      /* obey kernel semantics for fd return from open */
      if ((cmd == sIOopen || cmd == sIOopenPipe
           || cmd == sIOopenPortal || cmd == sIOopenString)
          && defaultfd != tofd) {
            /* this is always done for pipes, at least */
            /* set fd2 in prev. open for later checking */
            if (cmd == sIOopenPipe && (ioflags & O_CREAT)) {
                  /* pipefd is read end of the pipe */
                  pipefd = defaultfd;
                  fds[pipefd] = 1;
                  /* defaultfd is write end of pipe */
                  defaultfd = findfreefd();
                  command->doio->fd = defaultfd;
                  command->doio->fd2 = pipefd;
                  INSERTIO(&command->execio, command,sIOclose,pipefd,0,0);
            } else if (cmd == sIOopenPipe) {
                  fds[pipefd] = 0;
                  pipefd = -1;
            } else
                  command->doio->fd = defaultfd;
            /* copy free fd to tofd */
            INSERTIO(&command->doio, command, sIOdup, defaultfd, tofd, 0);
            /* close free fd */
            INSERTIO(&command->doio, command, sIOclose, defaultfd, 0, 0);
      }
      if (FDBUSY(tofd)) {
            /* arrange to close save-fd inside fork/exec */
            INSERTIO(&command->execio, command, sIOclose, savefd, 0, 0);
            /*
             * We need to restore original to-fd then close save-fd,
             * but due to mechanics of these things we have to append
             * them in reverse order here... don't get confused.
             */
            /* in parent, need to close save-fd */
            APPENDIO(&command->undoio, command, sIOclose, savefd, 0);
            /* but first need to restore original to-fd */
            if (tofd <= 2
                && (siofds[tofd] == NULL
                             || siofds[tofd]->_sb_refcnt == 0)) {
                  APPENDIO(&command->undoio, command, sIObufFree, tofd,0);
            }
            APPENDIO(&command->undoio, command, sIOdup, savefd,tofd);
            if (tofd <= 2
                && (siofds[tofd] == NULL
                             || siofds[tofd]->_sb_refcnt == 0)) {
                  APPENDIO(&command->undoio, command, sIObufFree, tofd,0);
            }
      }
      fds[tofd] = (cmd != sIOclose);
      /* remember this command said something about tofd */
      if (tofd < ((sizeof command->fdmask) * 8))
            command->fdmask |= (1<<tofd);
      if (intobufflag) {
            cmd = sIOintoBuffer;
            intobufflag = 0;
            /*
             * After forking the command we want to
             * sit and wait on the output down in RUNIO.
             */
            INSERTIO(&command->undoio, command, cmd, pipefd, 0, 0);
            /* and then we have to close it of course */
            INSERTIO(&command->undoio, command, sIOclose, pipefd, 0, 0);
            /*
             * we can't release pipefd (and fds[pipefd])
             * until after all the other doio's have been
             * taken care of... but we don't know when
             * that will be except for at next CommandPop.
             */
      }
      if (isset('R'))
            fprintf(stderr, "end(%x)\n", command);
}

/*
 * The interpreter interface to the execute() routine.
 */

STATIC void runcommand __((struct osCmd *, struct osCmd *, int *, const char *));
STATIC void
runcommand(c, pc, retcodep, cmdname)
      struct osCmd *c, *pc;
      int *retcodep;
      const char *cmdname;
{
      int ioflags;
      GCVARS1;

      GCPRO1(c->argv);
      if (c->argv) {    /* from tconc into list */

            if (!LIST(c->argv)) *((int*)0) = 0; /* ZAP! */

            c->argv = copycell(car(c->argv));
            cdr(c->argv) = NULL;
      }
      c->buffer = NULL;
      c->bufferp = &(c->buffer);

      /* in a backquote and output hasn't been explicitly redirected */
      if (ibt >= 0 && c->argv != NULL && !(c->fdmask & (1<<1))) {
            /*
             * If the current command isn't a builtin and stdout not
             * yet assigned, set up stdout as a pipe and read it
             * back at backquote commandpop.  This will work even if
             * we're dealing with a sequence, e.g. `a | b ; c | d`
             * and b is the current (non-builtin) command, because multiple
             * calls to readstring() just append to the buffer.
             * If any of b or d are builtins, the output will be
             * that of the later builtin.
             */

            /* XX: is this necessary*/
            ib_command[ibt]->doio = ib_command[ibt]->execio = NULL;

            if (c->shcmdp != NULL || c->sfdp != NULL) {
                  /* builtin function */
                  const char *name = "<builtin cmd>";
                  ioflags = 0;

                  INSERTIO(&c->doio, c, sIObufOut, 1, 1, 0);
                  INSERTIO(&ib_command[ibt]->undoio,
                         ib_command[ibt], sIObufIn, 0, 1, 1);
                  INSERTIO(&ib_command[ibt]->undoio,
                         ib_command[ibt], sIObufString, 0, 0, 1);
                  INSERTIO(&ib_command[ibt]->undoio,
                         ib_command[ibt], sIObufFree, 0, 0, 1);
            } else {
                  /* ok, ok, create the silly pipe! */
                  register struct IOop *iop;

                  if (c->doio) {
                        RUNIO(c->doio);
                        freeio(c->doio, 1);
                        c->doio = NULL;
                  }
                  ioop(sIOintoBuffer, ib_command[ibt], NULL,
                       O_CREAT|O_WRONLY|O_TRUNC, 1, NULL);
                  /* Now copy doio and execio to current command */
                  /* first doio */
#if 0
                  for (iop = ib_command[ibt]->doio; iop != NULL; iop = iop->next)
                        if (iop->next == NULL)
                              break;
                  if (iop != NULL) {
                        iop->next = c->doio;
#endif
                        c->doio = ib_command[ibt]->doio;
                        ib_command[ibt]->doio = NULL;
#if 0
                  }
#endif
                  /* then execio */
                  for (iop = ib_command[ibt]->execio; iop != NULL; iop = iop->next)
                        if (iop->next == NULL)
                              break;
                  if (iop != NULL) {
                        iop->next = c->execio;
                        c->execio = ib_command[ibt]->execio;
                        ib_command[ibt]->execio = NULL;
                  }
            }
            --ibt;
      }

      /* run the command and undo any temporary variables */
      if (retcodep != NULL) {
            const char *name = "<builtin cmd>";
            if (c->shcmdp != NULL)
              name = c->shcmdp->name;
            *retcodep = execute(c, pc, *retcodep, name);
            if (*retcodep != 0 && isset('e'))
                  trapexit(*retcodep);
      }
      /* else we are ignoring execution of this command */

      /*
       * We don't need to explicitly free the argv/envold/io
       * lists since that is taken care of by the setlevel()
       */
      if (c->doio)
            freeio(c->doio, 1);
      if (c->undoio)
            freeio(c->undoio, 1);
      if (c->execio)
            freeio(c->execio, 0);
      UNGCPRO1;
}

/*
 * Variable assignment routine.  This is used directly by the interpreter
 * and indirectly through v_set() by most everything else.  All new variables
 * are created in the global (but non-exported) scope unless variables are
 * automatically exported.  The prior value of a variable is sometimes stashed
 * away for later restoration.  This is only done when a command descriptor
 * is passed, since it is used to undo the effect of temporary variable
 * assignments on the command line.
 */

void
assign(sl_lhs, sl_rhs, command)
      conscell *sl_lhs, *sl_rhs;
      struct osCmd *command;
{
      conscell *s = NULL, *l = NULL;
      GCVARS4;

      GCPRO4(s, l, sl_lhs, sl_rhs);

      /*
       * Add (lhs oldvalue) to a list "envold" kept in the command.
       * Variable values are really (potentially) lists.
       */
      s = v_find(sl_lhs->cstring);

      l = copycell(sl_rhs); /* Copy it just in case.. */
      /* that was: s_copy_tree(), but we don't need THAT! */
      if (l)
        sl_rhs = l;

      if (s == NULL) {

        /* We don't know this variable, we create it */

        /* create the variable in the global but non-exported scope */
        for (l = car(envarlist); cddr(l) != NULL; l = cdr(l))
          continue; /* Scan scopes, stop at next to last */

        /* l points at the next-to-last sublist of envarlist */
        if (isset('a'))
          l = cdr(l);   /* or the list of exports */

        cdr(sl_rhs) = car(l);       /* the scope-list follows this value */
        s = copycell(sl_lhs);       /* Variable name here */
        cdr(s) = sl_rhs;            /* .. value follows varname */
        car(l) = s;                 /* .. and anchor varname into scope */
        l = NIL; /* A NIL-cell for old value.. */

      } else {

        /* The variable exists, we replace the data content */


        cdr(sl_rhs) = cddr(s);      /* Glue the chain:
                                 s -> new_value -> old_tail */
        l = cdr(s);                 /* Old value cell   */
        cdr(l) = NULL;        /* ... disconnected */

        cdr(s) = sl_rhs;            /* ... and place the new value in */

#ifdef      MAILER
        if (v_accessed)
          v_written(s);
#endif      /* MAILER */

        if (isset('a'))       /* if "auto-export" set */
          v_export(sl_lhs->cstring);      /* ick! */

      }
      /* stash old value */
      if (command != NULL) {
        cdr(l) = command->envold; command->envold = l;
        s      = copycell(sl_lhs);
        cdr(s) = command->envold; command->envold = s;
      }
      UNGCPRO4;

      /* fvcache.namesymbol = 0; */
      v_sync(sl_lhs->cstring);

      if (isset('I')) {
        fprintf(runiofp, "Assign %s = ", sl_lhs->cstring);
        s_grind(sl_rhs, runiofp);
        putc('\n', runiofp);
      }

#ifdef      MAILER
      if (D_assign) {
        fprintf(stderr, "%*s%s=", 4*funclevel, " ", sl_lhs->cstring);
        s_grind(sl_rhs, stderr);
        fputc('\n', stderr);
      }
#endif      /* MAILER */

}

/*
 * Discard the definition of the function with the given name.  If this was
 * the last definition for the stored code table, free the table.
 */

STATIC void undefun __((const char *));
STATIC void
undefun(fname)
      const char *fname;
{
      struct spblk *spl;
      
      spl = sp_lookup(symbol(fname), spt_funclist);
      if (spl == NULL)
        return;
      xundefun(spl);
}

/*
 * A version of undefun() we can call from inside sp_scan()
 */

int
xundefun(spl)
      struct spblk *spl;
{
      struct sslfuncdef *sfdp, **psfdp;
#ifdef      MAILER
      int idx;
      regexp **rep, **repstart;
      tregexp **trep, **trepstart;
#endif      /* MAILER */
      
      sfdp = (struct sslfuncdef *)spl->data;
      if (sfdp != NULL) {
        psfdp = &(sfdp->tabledesc->functions);
        for (sfdp = *psfdp; sfdp != NULL;
             psfdp = &sfdp->next, sfdp = *psfdp) {
          if (strcmp(sfdp->name,
                   ((struct sslfuncdef *)spl->data)->name) == 0) {
            *psfdp = sfdp->next;
            break;
          }
        }
        if (sfdp->tabledesc->functions == NULL &&
            sfdp->tabledesc->oktofree) {
#ifdef      MAILER
          repstart = sfdp->tabledesc->rearray;
          if (repstart != NULL) {
            idx = sfdp->tabledesc->rearray_idx;
            rep = repstart;
            while (rep - repstart < idx && *rep != NULL)
            free_regexp(*rep++);
            free((char *)sfdp->tabledesc->rearray);
          }
          trepstart = sfdp->tabledesc->trearray;
          if (trepstart != NULL) {
            idx = sfdp->tabledesc->trearray_idx;
            trep = trepstart;
            while (trep - trepstart < idx && *trep != NULL)
            free_tregexp(*trep++);
            free((void *)sfdp->tabledesc->trearray);
          }
#endif      /* MAILER */
          free((void *)sfdp->tabledesc->table);
          free((void *)sfdp->tabledesc);
        }
        free((void *)sfdp);
        /*
         * This should really be sp_delete, but if it is we won't
         * be able to call undefun() inside an sp_scan...
         */
        spl->data = NULL;
      }
      return 0;
}

/*
 * Define an interpreted function.  Sets up mutual links between the function
 * descriptor and the code table descriptor.
 */

STATIC void defun __((struct codedesc *, const char *, const char *, const char *));
STATIC void
defun(cdp, fname, position, eofunc)
      struct codedesc *cdp;
      const char  *fname;
      const char  *position, *eofunc;
{
      struct sslfuncdef *sfdp;
      
      undefun(fname);
      sfdp = (struct sslfuncdef *)emalloc(sizeof (struct sslfuncdef));
      /*
       * The function name string is known to be part of the function
       * pseudo-code and will not disappear unless the data block containing
       * the function definition goes bye-bye.
       */
      sfdp->name = fname;
      sfdp->pos  = position;
      sfdp->eot  = eofunc;
      sfdp->tabledesc = cdp;  /* will be set properly at end of interpret() */
      sfdp->next = cdp->functions;
      cdp->functions = sfdp;
      sp_install(symbol(fname), (void *)sfdp, 0, spt_funclist);
}

/*
 * This routine determines possible internal definitions of a function.
 */

void
functype(fname, shcmdpp, sfdpp)
      register const char *fname;
      struct shCmd **shcmdpp;
      struct sslfuncdef **sfdpp;
{
      spkey_t symid;
      struct spblk *spl;

      symid = symbol_lookup(fname);
      /* is it a defined function? */
      spl = NULL;
      if (symid)
        spl = sp_lookup(symid, spt_funclist);
      if (sfdpp) {
        if (spl != NULL)
          *sfdpp = (struct sslfuncdef *)spl->data;
        else
          *sfdpp = NULL;
      }

      /* behaviour in execute() requires we continue, not return */

      /* is it a builtin command? */
      spl = NULL;
      if (symid)
        spl = sp_lookup(symid, spt_builtins);
      if (shcmdpp != NULL) {
        if (spl != NULL)
          *shcmdpp = (struct shCmd *)spl->data;
        else
          *shcmdpp = NULL;
      }
      /* it must be a unix program */
}


/*
 * Coalesces all the small buffers in command->buffer into a single buffer.
 */

STATIC void coalesce __((struct osCmd *));
STATIC void
coalesce(command)
      struct osCmd *command;
{
      conscell *s;

      if (command->buffer == NULL)
        return;
      for (s = command->buffer; s != NULL; s = cdr(s))
        if (LIST(s))
          return;

      command->buffer->flags |= QUOTEDSTRING;   /* so result will be too */
      if (cdr(command->buffer) != NULL)
        /* this is only done for LARGE intoBuffer outputs */
        command->buffer = s_catstring(command->buffer);
      command->bufferp = &cdr(command->buffer);
}


/*
 * Chop off any leading and trailing whitespace.
 */

STATIC void flushwhite __((struct osCmd *));
STATIC void
flushwhite(command)
      struct osCmd *command;
{
      register conscell *s, *p;
      register char *cp;

      if (command->buffer == NULL)
        return;
      if (ISELEMENT(command->buffer))
        return;
      for (s = command->buffer; s != NULL; s = cdr(s))
        if (LIST(s))
          return;

#if 0
      /* strip leading whitespace */
      s = command->buffer;
      do {
        for (cp = command->buffer->string;
             *cp != '\0' && WHITESPACE(*cp); ++cp)
          continue;
      } while ((*cp == '\0') && (command->buffer = cdr(command->buffer)));
      if (command->buffer == NULL) {
        command->bufferp = &command->buffer;
        return;
      }
      if (s->string != cp) {
        command->buffer->string = strsave(cp);
      }
#endif

      /* strip trailing whitespace */
      p = NULL;
      do {
        for (s = command->buffer; cdr(s) != p; s = cdr(s))
          continue;
        for (cp = s->string + s->slen - 1;
             cp >= s->string; --cp)
          if (*cp != '\n' /* !WHITESPACE(*cp) */)
            break;
        p = s;
      } while ((cp < s->string) && (s != command->buffer));
      if (cp < s->string) {
        command->buffer = NULL;
        command->bufferp = &command->buffer;
      } else {
        *++cp = '\0';
        s->slen  = cp - s->string;
        cdr(s)   = NULL;
        command->bufferp = &cdr(s);
      }
}

STATIC void tsetsubexps __((struct si_retab **, tregexp *));
STATIC void
tsetsubexps(sepp, tre)
      struct si_retab **sepp;
      tregexp *tre;
{
      register struct si_retab *sep, *psep;
      register unsigned int i;

      for (sep = *sepp, psep = NULL; sep != NULL;
           psep = sep, sep = sep->next) {
        if (sep->trep == tre)
          break;
      }
      if (sep == NULL) {
        sep = (struct si_retab *)tmalloc(sizeof (struct si_retab));
        for (i = 0; i < (sizeof sep->startp)/(sizeof sep->startp[0]); ++i)
          sep->startp[i] = sep->endp[i] = NULL;
        memset((char *)sep, 0, sizeof (struct si_retab));
        sep->trep = tre;
        sep->next = *sepp;
      } else if (psep != NULL) {
        psep->next = sep->next;
        sep->next = *sepp;
      }
      *sepp = sep;
      tre->startp = sep->startp;
      tre->endp = sep->endp;
}


STATIC void setsubexps __((struct si_retab **, regexp *));
STATIC void
setsubexps(sepp, prog)
      struct si_retab **sepp;
      regexp *prog;
{
      register struct si_retab *sep, *psep;

      for (sep = *sepp, psep = NULL; sep != NULL;
           psep = sep, sep = sep->next)
        if (sep->rep == prog)
          break;
      if (sep == NULL) {
        sep = (struct si_retab *)tmalloc(sizeof (struct si_retab));
        memset((char *)sep, 0, sizeof (struct si_retab));
        if (prog != NULL)
          sep->match = (const char **) tmalloc((prog->re.re_nsub+1) *
                                     sizeof(char *));
        else
          sep->match = NULL;
        sep->rep = prog;
        sep->next = *sepp;
      } else if (psep != NULL) {
        psep->next = sep->next;
        sep->next = *sepp;
      }
      *sepp = sep;
      if (prog != NULL)
        prog->match = sep->match;
}


#if 0
/*
 * This is a cache structure for v_find() which has appreciable locality.
 * Every time an assignment is made or a scope pushed/popped, this cache
 * is invalidated.
 */
struct {
      int   namesymbol;
      conscell *location;
} fvcache = { 0, NULL };
#endif

struct loopinfo {
      int   brk;        /* relative pc address if we want to break */
      int   cont;       /* relative pc address if we want to continue */
      short cmdindex;   /* index of active command at this place */
      short varindex;   /* index of active loop variable */
};

STATIC token822 *tscanstring __((const char *));
STATIC
token822 *
tscanstring(s)
      const char *s;
{
      const char *cp;
      int len;
      token822 *t;

      /* fprintf(stderr,"tscanstring('%s') ", s); */
      t = HDR_SCANNER(s);

      if (t != NULL && t->t_next == NULL && t->t_type == String) {
        /* we need to de-quote the quoted-string */
        char *bp;
        const char *buf;
        len = TOKENLEN(t);
        buf = bp = (char *)tmalloc(len+3);
#ifdef NO_DEQUOTE_AT_SIFTS
        *bp++ = '"';
#endif
        for (cp = t->t_pname; (cp - t->t_pname) < len ; ++cp) {
#if 0 /* NOTE: No \-dequotation processing ! */
          if ((*cp == '\\') && ((cp - t->t_pname) < len-1))
            *bp++ = *++cp;
          else
#endif
            *bp++ = *cp;
        }
#ifdef NO_DEQUOTE_AT_SIFTS
        *bp++ = '"';
#endif
        *bp = '\0';
        /* fprintf(stderr,"dequote -> '%s'", buf); */
        t = HDR_SCANNER(buf);
      }
      /* fprintf(stderr,"\n"); */
      return t;
}

/* This is a last-line defence on utter stupidity -- doing recursive
 * evaluation on something without proper terminating condition does
 * quickly lead to awfull crash with a system whose stack is demolished,
 * and rather difficult to debug...   Trust me, I know -- Matti Aarnio */

STATIC int max_interpreter_recursions = 40;

/*
 * Interpret Shell pseudo-code generated from S/SL description.
 *
 * Memory allocation and freeing in this code is a bit funny.  To avoid
 * a complicated reference-count or other GC scheme, we maintain a stack
 * in memory (type MEM_SHCMD).  We unravel the stack as we pop commands.
 * That takes care of scratch allocations, which is everything except
 * variable names and values.  We have to be careful about freeing that
 * data (which is malloc()'ed) whenever we discard it.
 */

struct codedesc *
interpret(Vcode, Veocode, Ventry, caller, retcodep, cdp)
      const void *Vcode, *Veocode, *Ventry;
      struct osCmd *caller;
      int     *retcodep;
      struct codedesc *cdp;
{
      register const char *code = Vcode, *eocode = Veocode, *entry = Ventry;
      register const char  *pc;
      register OutputTokens cmd;
      register struct osCmd *command;
      register int commandIndex, variableIndex, i;
      const char *arg1 = NULL, *name, *cmdname;
      const char *origlevel;
      int   *iname;
      int   argi1 = 0, dye, childpid, dollar, ioflags;
      memtypes stickytmp, origstickymem;
      int   defaultfd, quote, quoted, nloop, ignore_level;
#define LOOPMAXDEPTH 30
      struct loopinfo loop[LOOPMAXDEPTH]; /* max # nested loops */
      conscell *variable = NULL, *l = NULL, *margin = NULL;
      conscell *d = NULL, *tmp = NULL;
      struct osCmd *prevcommand = NULL;
      struct osCmd *pcommand    = NULL;
#define COMMANDMAXDEPTH 30
      struct osCmd commandStack[COMMANDMAXDEPTH];
#define VARMAXDEPTH 30
      conscell *varmeter = NULL;
      conscell *varmchain = NULL;
#ifdef      MAILER
      struct siftinfo sift[30];
      int nsift = -1;
      regexp   *re = NULL;
      tregexp *tre = NULL;
#endif      /* MAILER */
      GCVARS6;

      zoptind = 0;      /* for zgetopts() */
      commandIndex  = -1;
      command = &commandStack[++commandIndex];
      command->buffer = NULL;
      command->bufferp = &command->buffer;
      command->flag = 0;
      command->rval = command->argv = command->envold = NULL;
      variableIndex = -1;
      variable = NULL;
      ioflags = 0;
      defaultfd = 0;
      dye = 0;
      dollar = 0;
      quote = 0;
      varmeter = NULL;
      fds[0] = fds[1] = fds[2] = 1;   /* XX: this isn't recursive, eh?? */

      margin = car(envarlist);/* so we can be sure to pop scopes on exit */
#define     MAGIC_LARGE_IGNORE_LEVEL      123435      /* >> any valid ignore level */
      ignore_level = MAGIC_LARGE_IGNORE_LEVEL;
#define     MAGIC_LARGE_ADDRESS     9827432           /* >> any valid address */
      loop[0].brk = loop[0].cont = MAGIC_LARGE_ADDRESS;
      loop[0].varindex = -1;
      loop[0].cmdindex = 0;   /* this is important to unravel on "return" */
      nloop = 0;
      origstickymem = stickymem;
      origlevel = getlevel(MEM_SHCMD);
      stickymem = MEM_SHCMD;
      /* shut up the compiler */
      stickytmp = MEM_SHCMD;
      d = NULL;
      argi1 = 0;

      if (entry == NULL)
            pipefd = -1;

      if (cdp == NULL) {
        cdp = (struct codedesc *)emalloc(sizeof (struct codedesc));
        cdp->table   = code;
        cdp->eotable = eocode;
        cdp->functions = NULL;
#ifdef      MAILER
        cdp->rearray = NULL;
        cdp->rearray_size = 0;
        cdp->rearray_idx = -1;
        cdp->trearray = NULL;
        cdp->trearray_size = 0;
        cdp->trearray_idx = -1;
#endif      /* MAILER */
        cdp->oktofree = 0;
      }
#ifdef      MAILER
      ++funclevel;
      if (funclevel > max_interpreter_recursions) {
        fprintf(stderr,"zmailer: interpret: recursed more than %d levels deep on invocation!  script termination condition error ?\n",max_interpreter_recursions);
        zsyslog((LOG_EMERG,"zmailer: interpret: recursed more than %d levels deep on invocation!  script termination condition error ?\n",max_interpreter_recursions));
        abort(); /* excessively deep recursion - *.cf -script termination condition error ? */
      }
#endif      /* MAILER */

      GCPRO6(varmchain, variable, l, margin, d, tmp);

      /* Initialize syntax for regular expressions */
      (void) re_set_syntax(RE_CONTEXT_INDEP_ANCHORS |
                       RE_CONTEXT_INDEP_OPS |
                       RE_CONTEXT_INVALID_OPS |
                       RE_NO_BK_PARENS |
                       RE_NO_BK_VBAR |
                       RE_DOT_NOT_NULL);

#ifdef DEBUGxx
      fprintf(stderr,"%s:%d &command->buffer=%p\n",__FILE__,__LINE__,
            &command->buffer);
#endif
      GCPRO4STORE(command->,
                command->argv, command->rval,
                command->envold, command->buffer);

      /* funcall tracing could be done here */
      /* if (caller != NULL) grindef("ARGV = ", caller->argv); */
      if (isset('R'))
            fds[FILENO(stderr)] = 1;
      for (pc = (entry == NULL ? code : entry) ; pc < eocode; ++pc) {
            if (sprung) {
                  trapped();
                  if (interrupted)
                        break;
            }
            cmd = (OutputTokens)(*pc & 0xFF);
            if (isset('I'))
                  fprintf(runiofp, "'%d\t%s\n", pc - code,
                        TOKEN_NAME(cmd));
            switch (TOKEN_NARGS(cmd)) {
            case 0:
                  arg1  = NULL;
                  argi1 = 0;
                  break;
            case 1:
                  argi1 = 0;
                  arg1 = (const char*) ++pc;
                  while (*pc != '\0')
                        ++pc;
                  break;
            case -1:
                  arg1   = NULL;
                  argi1  = (*++pc) & 0xFF;
                  argi1 <<= 8;
                  argi1 |= (*++pc) & 0xFF;
                  argi1 <<= 8;
                  argi1 |= (*++pc) & 0xFF;
                  argi1 <<= 8;
                  argi1 |= (*++pc) & 0xFF;
                  break;
            }

            switch (cmd) {
            case sBufferSetFromArgV:
                  dollar = 1;
                  quote = 1;
                  arg1 = "@";
                  /* this gives "$@" */
                  /* FALL THROUGH */
            case sBufferSet:
                  /* The buffer points at a linked list of strings */
                  command->buffer  = NULL;
                  command->bufferp = &command->buffer;
                  /* FALL THROUGH */
            case sBufferAppend:
                  if (dollar) {
                        dollar = 0;
                        d = v_expand(arg1, caller, *retcodep);
                        if (d == NULL) {
                              if (isset('u')) {
                                    fprintf(stderr,
                                        "%s: parameter not set\n",
                                        arg1);
                                    ignore_level = commandIndex;
                              }
                              d = conststring(uBLANK,0);
                        } else {
                              d = s_copy_chain(d);
                        }
                        if (!quote && STRING(d) && *(d->string) == '\0')
                              break;
                  } else if (*arg1 != '\0' || quote) {
                        int slen = strlen(arg1);
#if 0
                        /* XXX: If we can be sure of input
                           (arg1) being on stabile storage..
                           Command-line interpreter (shell)
                           is not stabile storage, but script
                           interpreter is! */
                        d = conststring(arg1,slen);
#else
                        d = newstring(dupnstr(arg1,slen),slen);
#endif
                  } else
                        break;      /* it is a null string! */
#ifdef DEBUGxx
fprintf(stderr,"%s:%d &command->buffer = %p\n",__FILE__,__LINE__,&command->buffer);
#endif
                  *command->bufferp = d;
                  for (tmp = d; cdr(tmp) != NULL; tmp = cdr(tmp))
                        continue;
                  command->bufferp = &cdr(tmp);
                  if (quote) {
                        while (d != NULL) {
                              if (STRING(d) && !ISDONTQUOTE(d))
                                    d->flags |= QUOTEDSTRING;
                              d = cdr(d);
                        }
                        quote = 0;
                  }
                  if (isset('I'))
                        grindef("Buffer = ", command->buffer);
                  break;
            case sBufferExpand:
                  if (command->buffer == NULL)
                        d = conststring(uBLANK,0);
                  else if (cdr(command->buffer))
                        d = s_catstring(command->buffer);
                  else
                        d = command->buffer;
                  quoted = ISQUOTED(d);
                  d = v_expand((const char*)d->string,caller,*retcodep);
                  if (d == NULL) {
                        if (isset('u')) {
                              fprintf(stderr,
                                    "%s: parameter not set\n",
                                    arg1);
                              ignore_level = commandIndex;
                        }
                        d = conststring(uBLANK,0);
                  } else {
                        d = s_copy_chain(d);
                  }
                  command->buffer = d;
                  while (d != NULL) {
                        if (quoted && STRING(d))
                              d->flags |= QUOTEDSTRING;
                        if (cdr(d) == NULL)
                              break;
                  }
                  if (d == NULL)
                        command->bufferp = &command->buffer;
                  else {
                        for (tmp = d; cdr(tmp) != NULL; tmp = cdr(tmp))
                              continue;
                        command->bufferp = &cdr(tmp);
                  }
                  if (isset('I'))
                        grindef("Expanded Buffer = ", command->buffer);
            case sArgVpush:
                  if (command->buffer == NULL)
                        break;
                  d = expand(command->buffer,1);
                  if (command->argv == NULL && STRING(d)) {
                        /* what kind of command is this? */
                        functype(d->string,
                               &command->shcmdp, &command->sfdp);
                        if (command->sfdp != NULL)
                              command->shcmdp = NULL;
                        if (prevcommand != NULL) {
                              prevcommand->next = command;
                              prevcommand->reaperTop = reapableTop;
                              if (command->shcmdp == NULL
                                  && command->sfdp == NULL) {
                                    /* create pipe for prev. cmd */
                                    ioop(sIOopenPipe, prevcommand,
                                         NULL, O_CREAT, 1, NULL);
                                    /* --- */
                                    RUNIO(prevcommand->doio);
                                    freeio(prevcommand->doio, 1);
                                    prevcommand->doio = NULL;
                                    /* --- */
                                    /* add inpipe to this command */
                                    ioop(sIOopenPipe, command,
                                         NULL, O_RDONLY, 0, NULL);
                              } else if (command->sfdp != NULL  ||
                                       (command->shcmdp != NULL &&
                                        (command->shcmdp->sptr != NULL ||
                                         prevcommand->shcmdp->sptr != NULL))){
                                    /* create stringbuffer */
                                    name = NULL;
                                    INSERTIO(&prevcommand->doio,
                                           prevcommand,
                                           sIObufOut, 1, 1, 0);
                                    /* --- */
                                    RUNIO(prevcommand->doio);
                                    freeio(prevcommand->doio, 1);
                                    prevcommand->doio = NULL;
                                    /* --- */
                                    INSERTIO(&command->doio,
                                           command,
                                           sIObufIn, 0, 1, 0);
#if 1
                                    if (nloop > 0 &&
                                        loop[nloop].cmdindex == 
                                          commandIndex-1)
                      INSERTIO(&commandStack[commandIndex-2].undoio,
                             &commandStack[commandIndex-2],
                             sIObufFree, 0, 0, 0);
                                    else
#endif
                                      INSERTIO(&command->undoio,
                                             command,
                                             sIObufFree,0,0,0);
                              }
                              /*
                               * ... else we're connecting two
                               * list-valued functions, which is ok.
                               */
                              cmdname = NULL;
                              if (prevcommand->sfdp)
                                cmdname = prevcommand->sfdp->name;
                              if (prevcommand->shcmdp)
                                cmdname = prevcommand->shcmdp->name;
                              runcommand(prevcommand, caller,
                                       ignore_level>commandIndex ?
                                       retcodep : NULL, cmdname);
                              command->rval = prevcommand->rval;
                              if (command->prev == prevcommand) {
                                    command->prev = NULL;
                                    command->flag |= OSCMD_SKIPIT;
                              }
                              free((char *)prevcommand);
                              prevcommand = NULL;
                              /* X:shouldn't prevcommand be stacked?*/
                        }
                  }

                  GCPLABPRINTis(command->gcpro4);

                  if (command->argv == NULL) {
                        command->argv = ncons(d);
                        command->argv = ncons(command->argv);
                  } else {
                        cddar(command->argv) = d;
                  }
                  cdar(command->argv) = s_last(d);

                  if (command->iocmd == ioPipeLater) {
                        /* we saw an openPipe but it was too early...*/
                        command->iocmd = ioNil;
                        if (command->shcmdp != NULL
                            || command->sfdp != NULL) {
                              /* set prevcommand in the CommandPop */
                              command->iocmd = ioPipeOutput;
                        } else {
                              command->reaperTop = reapableTop;
                              ioop(sIOopenPipe, command,
                                   NULL, O_CREAT, 1, NULL);
                        }
                  }
                  if (isset('I'))
                        grindef("Argv = ", command->argv);
                  break;
            case sArgList:
                  /* take the remaining arguments in caller->argv
                     and stick them in the local variable "argv" in
                     the current scope */
                  break;
            case sVariableCdr:
                  if (variable != NULL)
                        car(variable) = cdar(variable);
                  if (varmeter) {
                        if (variable && car(variable)) {
                              car(varmeter) = caar(variable);
                              varmeter->flags = car(variable)->flags;
                              varmeter->slen  = car(variable)->slen;
                        } else {
                              car(varmeter) = NULL;
                              varmeter->flags = 0;
                        }
                  }
                  if (isset('I'))
                        grindef("Variable = ", variable);
                  break;
            case sVariablePush:
                  if (variableIndex >= 0) {
                    tmp = ncons(varmeter);  cdr(tmp) = varmchain; varmchain = tmp;
                    tmp = ncons(variable);  cdr(tmp) = varmchain; varmchain = tmp;
                  }

                  ++variableIndex;

                  if (variableIndex >= VARMAXDEPTH) {
                    fprintf(stderr,"%s: interpret.c: varStack[] recursed once too many. Max depth: %d\n",
                          progname, VARMAXDEPTH);
                    abort(); /* varStack[] recursed too deep! */
                  }

                  if (command->buffer == NULL)
                        variable = NULL;
                  else {
                        variable = expand(command->buffer,2);
                        variable = ncons(variable);
                  }

                  varmeter = NULL;
                  if (isset('I'))
                        grindef("Variable = ", variable);
                  break;
            case sVariablePop:
                  --variableIndex;
                  if (varmchain) {
                        variable = car(varmchain);
                        varmeter = cadr(varmchain);
                        varmchain = cddr(varmchain);
                        if (isset('I'))
                              grindef("Variable = ", variable);
                  }
                  else
                    variable = varmeter = NULL;

                  break;
            case sVariableBuffer:
                  if (variable != NULL) {
                        command->buffer = ncons(car(variable));
                  } else
                        command->buffer = NIL;
                  if (isset('I'))
                        grindef("Buffer = ", command->buffer);
                  break;
            case sVariableAppend:
                  if (command->buffer == NULL)
                        break;
                  d = expand(command->buffer,3);
                  if (variable == NULL)
                        variable = ncons(d);
                  else if (car(variable) == NULL)
                        car(variable) = d;
                  else
                        cdr(s_last(car(variable))) = d; /* XX */
                  if (isset('I'))
                        grindef("Variable = ", variable);
                  break;
            case sVariableLoopAttach:
                  varmeter = cdaar(envarlist);
                  if (variable != NULL) {
                        car(varmeter) = caar(variable);
                        varmeter->flags = car(variable)->flags;
                        varmeter->slen  = car(variable)->slen;
                  }
                  break;
            case sCommandPush:
                  if (commandIndex > 0) {
                        if (command->iocmd == ioPipeLater) {
                              /* see above at end of ArgV */
                              command->iocmd = ioNil;
                              if (command->shcmdp != NULL
                                  || command->sfdp != NULL)
                                    command->iocmd = ioPipeOutput;
                              else {
                                    command->reaperTop =
                                          reapableTop;
                                    ioop(sIOopenPipe, command,
                                         NULL, O_CREAT, 1, NULL);
                              }
                        }
                        if (command->doio != NULL) {
                              RUNIO(command->doio);
                              freeio(command->doio, 1);
                              command->doio = NULL;
                              if (prevcommand != NULL) /* gross */
                                    prevcommand->doio = NULL;
                        }
                  }
                  pcommand = command;
                  command = &commandStack[++commandIndex];
                  if (commandIndex >= COMMANDMAXDEPTH) {
                    fprintf(stderr,"%s: interpret.c: commandStack[] recursed once too many. Max depth: %d\n",
                          progname, COMMANDMAXDEPTH);
                    abort(); /* commandStack[] recursed too deep! */
                  }

                  memset(command, 0, sizeof(*command));
                  command->bufferp = &command->buffer;
                  /* command->argv = command->envold =
                     command->rval = command->buffer = NULL;
                     command->doio = command->undoio = 0;
                     command->execio = command->fdmask = 0;
                     command->pgrp = 0;
                     command->flag = 0;
                     command->shcmdp = NULL;
                     command->sfdp = NULL;
                     command->next = NULL;
                  */

                  command->iocmd = ioNil;
#ifdef DEBUGxx
fprintf(stderr,"%s:%d &command->buffer=%p\n",__FILE__,__LINE__,
      &command->buffer);
#endif
                  GCPRO4STORE(command->,
                            command->argv, command->rval,
                            command->envold, command->buffer);

                  if (prevcommand != NULL) {    /* in pipe */
                        command->memlevel = prevcommand->memlevel;
                        command->pgrpp = prevcommand->pgrpp;
                  } else {
                        command->memlevel = getlevel(MEM_SHCMD);
                        command->pgrpp = NULL;
                  }
                  command->prev = prevcommand;
                  if (commandIndex > 1) {
                        /*
                         * This value is inherited, NULL or not.  It
                         * is reset by a null return from list-valued
                         * function, or by any string-valued function.
                         */
                        command->rval = pcommand->rval;

                        command->pgrpp = pcommand->pgrpp;
                        command->fdmask = pcommand->fdmask;

                        /* mark child commands of `...` */
                        if (pcommand->iocmd == ioIntoBuffer) {
                              pcommand->reaperTop = reapableTop;
                              command->reaperTop = reapableTop;
                        } else if (pcommand->reaperTop > -1)
                              command->reaperTop =
                                    pcommand->reaperTop;
                        else
                              command->reaperTop = -1;
                  } else {
                        command->reaperTop = -1;
                        if (command->prev != NULL)
                              command->rval = command->prev->rval;
                        else
                              command->rval = NULL;
                  }
                  break;
            case sCommandPop:
                  if (pipefd >= 0)   /* IOintoBuffer relies on this */
                        fds[pipefd] = 0;
                  if (command->iocmd == ioPipeOutput) {
                        /*
                         * This command pipes its output into
                         * another one.
                         */
                        command->iocmd = ioNil;
                        prevcommand =
                          (struct osCmd *)emalloc(sizeof(struct osCmd));
                        *prevcommand = *command;
                        command->flag |= OSCMD_SKIPIT;
                        break;
                  }
                  argi1 = *retcodep;
                  if (command->iocmd != ioCarryBuffer) {
                        cmdname = NULL;
                        if (command->sfdp)
                          cmdname = command->sfdp->name;
                        if (command->shcmdp)
                          cmdname = command->shcmdp->name;
                        runcommand(command, caller,
                                 ignore_level > commandIndex ?
                                 retcodep : NULL, cmdname);
                        tmp = *(command->bufferp);
                  }
                  if (ignore_level == commandIndex)
                        ignore_level = MAGIC_LARGE_IGNORE_LEVEL;
                  if (command->shcmdp != NULL
                      && (command->shcmdp->flag == SH_INTERNAL ||
                        (void*)command->shcmdp->rptr != NULL)) {
                        /* this was break, continue, return or exit */
                        /* any optional argument is now in *retcodep */
                        if (*command->shcmdp->name == 'b' || /*break*/
                            *command->shcmdp->name == 'c') { /*cont'*/
                              if (*retcodep > 0) {
                                    *retcodep -= 1;
                                    if (*retcodep <= nloop)
                                          nloop -= *retcodep;
                              } else
                                    *retcodep = 0;
                              if (nloop < 0)
                                    goto nobreak;
                              if (*command->shcmdp->name == 'b')
                                pc = code + loop[nloop].brk;
                              else
                                pc = code + loop[nloop].cont;
                              --pc;
                        } else if (*command->shcmdp->name == 'e') {
                              if (cdar(command->argv))
                                    trapexit(*retcodep);
                              else
                                    trapexit(argi1);
                        }
#if 0
                        else {
                              std_printf("ix = %d\nrval = %x\n",
                                     commandIndex, command->rval);
                        }
#endif
                        /* if not in a loop this is done for returns */
                        while (commandIndex > loop[nloop].cmdindex) {
                              pcommand = &commandStack[commandIndex];
                              if (pcommand->undoio) {
                                    RUNIO(pcommand->undoio);
                                    freeio(pcommand->undoio, 1);
                              }
                              /* this is done after getout, below */
                              /*setlevel(MEM_SHCMD, pcommand->memlevel);*/
#ifdef DEBUGxx
      fprintf(stderr,"%s:%d &command->buffer=%p\n",__FILE__,__LINE__,
            &command->buffer);
#endif
                              UNGCPROSTORE4( pcommand-> );
                              --commandIndex;
                        }
                        while (variableIndex > loop[nloop].varindex) {
                              if (varmeter) {
                                    car(varmeter) = NULL;
                                    varmeter->flags = 0;
                              }
                              if (--variableIndex < 0)
                                break;
                              if (varmchain) {
                                variable = car(varmchain);
                                varmeter = cadr(varmchain);
                                varmchain = cddr(varmchain);
                              } else
                                variable = varmeter = NULL;
                        }
                        if (isset('I'))
                              grindef("Variable = ", variable);

                        if (*command->shcmdp->name == 'b'
                            && variableIndex >= 0
                            && varmeter != NULL) {
                              car(varmeter) = NULL;
                              varmeter->flags = 0;
                              variableIndex--;
                              if (varmchain)
                                varmchain = cddr(varmchain);
                        }
                        if (*command->shcmdp->name == 'r') { /* ``r''eturn */
#if 0
                              std_printf("idx = %d\n", commandIndex);
                              if (commandIndex >= 0)
                                    std_printf("rval %x\n",
                                          command->rval);
#endif
                              goto getout;
                        } else {
                              command = &commandStack[commandIndex];
                              break;
                        }
                  }
nobreak:
                  if (command->prev != NULL
                      || (command->flag & OSCMD_SKIPIT)) {
                    UNGCPROSTORE4( commandStack[commandIndex]. );
                        --commandIndex;
                  }
                  UNGCPROSTORE4( commandStack[commandIndex]. );
                  --commandIndex;
#ifdef DEBUGxx
fprintf(stderr,"%s:%d commandIndex=%d\n",__FILE__,__LINE__,commandIndex);
#endif
                  /*
                   * If we are returning data, we shouldn't free dtpr
                   * or do a setlevel(), since the value may be used in
                   * parent command.  Eventually a setlevel() will be
                   * done anyway to reclaim space... we hope.
XXX: HERE! Must copy the output to PREVIOUS memory level, then discard
         the recursed one! <mea@utu.fi> ( command->rval )
                   */
                  if (command->memlevel && command->rval == NULL
                      && command->buffer == NULL
                      && command->iocmd != ioIntoBuffer) {
                        if (command->pgrp > 0)
                              jc_report(command->pgrp);
                  }
                  if (commandIndex > 0) {
                        pcommand = &commandStack[commandIndex];
                        while (command->argv != NULL &&
                               pcommand->flag & OSCMD_SKIPIT) {
                          UNGCPROSTORE4( commandStack[commandIndex]. );
                          pcommand = &commandStack[--commandIndex];
                        }
#ifdef DEBUGxx
      fprintf(stderr,"%s:%d &pcommand->buffer=%p\n",__FILE__,__LINE__,
            &pcommand->buffer);
#endif
                        pcommand->rval = command->rval;
                        if (command->buffer != NULL) {
                          GCVARS2;
                          GCPRO2(pcommand->buffer, command->buffer);
                              if (command->flag & OSCMD_QUOTEOUTPUT)
                                    coalesce(command);
                              else
                                    flushwhite(command);
                              *(pcommand->bufferp) = command->buffer;
                              pcommand->bufferp = command->bufferp;
                          UNGCPRO2;
                        }
                        command = pcommand;
                        if (isset('I'))
                              grindef("Command = ", command->argv);
                  }
                  break;
            case sCommandCarryBuffer:
                  command->iocmd = ioCarryBuffer;
                  break;
            case sIOopen:
            case sIOopenPortal:
            case sIOopenString:
                  if (command->buffer == NULL)
                        name = "";
                  else if (cdr(command->buffer))
                        name = (const char *) (s_catstring(command->buffer))->string;
                  else if (command->buffer)
                        /* always true, need else below */
                        name = (const char *) command->buffer->string;
                  else
                  /* FALL THROUGH */
            case sIOopenPipe:
                  if (cmd == sIOopenPipe) {
                        if (ioflags & O_CREAT) {
                              if (command->argv == NULL) {
                                    /* defer dealing with this */
                                    command->iocmd = ioPipeLater;
                                    break;
                              } else if (command->shcmdp != NULL
                                       || command->sfdp != NULL) {
                                    command->iocmd = ioPipeOutput;
                                    break;
                              }
                        } else if (prevcommand != NULL)
                              break;
                        name = NULL;
                  } else            /* continuation of above fallthrough */
                  /* FALL THROUGH */
            case sIOdup:
            case sIOclose:
                        name = NULL;
                  if (cmd == sIOclose)
                        ioflags = 0;
                  ioop(cmd, command, name, ioflags, defaultfd, arg1);
                  break;
            case sIOintoBuffer:
                  command->fdmask = 0;
                  if (quote) {
                        command->flag |= OSCMD_QUOTEOUTPUT;
                        quote = 0;
                  }
                  ib_command[++ibt] = command;
                  if (ibt > 20) {
                    fprintf(stderr,"interpret.c: Eccessive nested IOintoBuffer operations; over 20 of them in recursion!\n");
                    abort();
                  }
                  break;
            case sIOsetIn:
                  defaultfd = 0;
                  ioflags = O_RDONLY;
                  break;
            case sIOsetInOut:
                  defaultfd = 0;
                  ioflags = O_CREAT|O_RDWR;
                  break;
            case sIOsetOut:
                  defaultfd = 1;
                  ioflags = O_CREAT|O_WRONLY|O_TRUNC;
                  break;
            case sIOsetAppend:
                  defaultfd = 1;
                  ioflags = O_CREAT|O_WRONLY|O_APPEND;
                  break;
            case sIOsetDesc:
                  defaultfd = atoi((const char *)arg1);
                  break;
            case sParameter:
                  if (caller != NULL && caller->argv != NULL) {
                        int slen;
                        l = car(caller->argv);
                        if ((d = cdr(l)) != NULL) {
                              cdr(l) = cddr(l);
                              if (!LIST(d))
                                    name = (const char *)d->string;
                        } else
                              name = "";
                        if (d != NULL && LIST(d)) {
                          d = copycell(d);
                          cdr(d) = NULL;
                        } else {
                          slen = strlen(name);
                          d = newstring(dupnstr(name,slen),slen);
                        }
                        /* create the variable in the current scope */
                        l = car(envarlist);
                        cdr(d) = car(l);
                        if (*arg1) {
                          slen = strlen(arg1);
#if 0
                        /* XXX: If we can be sure of input
                           (arg1) being on stabile storage..
                           Command-line interpreter (shell)
                           is not stabile storage, but script
                           interpreter is! */
                          car(l) = conststring(arg1,slen);
#else
                          car(l) = newstring(dupnstr(arg1,slen),slen);
#endif
                        } else
                          car(l) = conststring(uBLANK,0);
                        cdar(l) = d;

                        /* grindef("ARGV = ", caller->argv);
                           grindef("VARS = ", envarlist);
                           grindef("TMPO = ", l);  */
                  } else {
                        fprintf(stderr, "parameter without call\n");
                        abort(); /* parameter without call! */
                  }
                  break;
            case sAssign:
            case sAssignTemporary:
                  if (command->argv == NULL ||
                      cdar(command->argv) == NULL)
                        break;
                  for (d = caar(command->argv); cdr(d) != NULL; )
                        d = cdr(d);
                  coalesce(command);
                  if (command->buffer == NULL)
                    command->buffer = conststring(uBLANK,0);
                  if (nsift >= 0)
                    v_accessed = sift[nsift].accessed;

                  if (cmd == sAssign)
                    assign(d, command->buffer, NULL);
                  else
                    assign(d, command->buffer, command);

                  /* do NOT reset *retcodep here */
                  command->bufferp = &command->buffer;
                  command->argv    = NULL;
                  command->shcmdp  = NULL;
                  if (isset('I'))
                        grindef("Argv = ", command->argv);
                  break;
            case sFunction:
                  if (isset('I'))
                        fprintf(runiofp,
                              "defining '%s', entry@ %d, exit@ %d cdp@ %p\n",
                              command->buffer->string,
                              pc + 1 - code, argi1, cdp);
                  defun(cdp, command->buffer->string, pc+1,
                        code + argi1);
                  /* FALL THROUGH */
            case sJump:
                  pc = code + argi1 - 1;
                  break;
            case sBranchOrigin:
                  fprintf(stderr, "%s: unpatched branch at %d\n",
                              progname, pc - code);
                  break;
            case sJumpFork:
                  if (command->doio != NULL) {
                        RUNIO(command->doio);
                        freeio(command->doio, 1);
                        command->doio = NULL;
                  }
                  if ((childpid = fork()) == 0) {
                        /* in child */
                        dye = 1;
                        eocode = code + argi1;
                        if (command->execio != NULL) {
                              RUNIO(command->execio);
                              freeio(command->execio, 0);
                              command->execio = NULL;
                        }
                        command->reaperTop = -1;
                        --ibt;
                  } else if (childpid > 0) {
                        /* in parent */
                        while (wait(NULL) != childpid)
                              continue;
                        pc = code + argi1 - 1;
                  } else if (childpid < 0) {
                        /* error */
                  }
                  break;
            case sJumpIfFailure:
                  if (*retcodep > 0)
                        pc = code + argi1 - 1;
                  break;
            case sJumpIfSuccess:
                  if (*retcodep == 0)
                        pc = code + argi1 - 1;
                  break;
            case sJumpIfNilVariable:
                  if (variable == NULL || car(variable) == NULL)
                        pc = code + argi1 - 1;
                  break;
            case sJumpIfMatch:
                  if (command->buffer == NULL)
                        break;
                  if (variable == NULL) {
                        variable = conststring(uBLANK,0);
                        variable = ncons(variable);
                  } else if (car(variable)->string == NULL)
                        break;
                  globchars['|'] = 1;
                  iname = NULL; name = NULL;
                  /* NOTE: Earlier we did GLOB processing on result!
                     Now we absolutely forbid it.. */
                  switch (squish(command->buffer,(char**)&name,&iname,-1)){
                  case -1:
                        if (STRING(command->buffer)
                            && strcmp(command->buffer->string,
                                    car(variable)->string) == 0)
                              pc = code + argi1 - 1;
                        break;
                  case 0:
                        if (strcmp(name, car(variable)->string) == 0)
                              pc = code + argi1 - 1;
                        break;
                  case 1:
                        i = 0;
                        do {
                              int fi = i;
                              while (iname[i] != 0
                                  && iname[i] != (u_int)'|')
                                    ++i;
                              if (glob_match(&iname[fi], &iname[i],
                                          car(variable)->string)) {
                                    pc = code + argi1 - 1;
                                    break;
                              }
                        } while (iname[i++] != 0);
                        break;
                  }
                  if (iname) free(iname);
                  globchars['|'] = 0;
                  break;
            case sJumpIfFindVarNil:
                  if (command->buffer == NULL)
                        d = conststring(uBLANK,0);
                  else if (cdr(command->buffer))
                        d = s_catstring(command->buffer);
                  else
                        d = command->buffer;
                  d = v_expand(d->string, caller, *retcodep);
                  if (d == NULL)
                        pc = code + argi1 - 1;
                  break;
            case sJumpIfOrValueNil:
                  if (d == NULL
                      || (cdr(d) != NULL && (d = cdr(d)) == NULL)
                      || (STRING(d)
                        && (d->string == NULL || *d->string == '\0'))
                      || (LIST(d) && car(d) == NULL))
                        pc = code + argi1 - 1;
                  break;
            case sJumpLoopBreak:
                  loop[nloop].brk = argi1;
                  break;
            case sJumpLoopContinue:
                  loop[nloop].cont = argi1;
                  break;
            case sLoopEnter:
                  ++nloop;
                  if (nloop >= LOOPMAXDEPTH) {
                    fprintf(stderr,"%s: interpret.c: sLoopEnter once too many times, max depth: %d\n",
                          progname, LOOPMAXDEPTH);
                    abort(); /* sLoopEnter too deep! */
                  }
                  loop[nloop].cont = 0;
                  loop[nloop].brk = 0;
                  loop[nloop].cmdindex = commandIndex;
                  loop[nloop].varindex = variableIndex;
                  if (prevcommand != NULL) {
                        prevcommand->next = command;
                        prevcommand->reaperTop = reapableTop;
                        /* create stringbuffer */
                        name = NULL;
                        INSERTIO(&prevcommand->doio,
                               prevcommand,
                               sIObufOut, 1, 1, 0);
                        /* --- */
                        RUNIO(prevcommand->doio);
                        freeio(prevcommand->doio, 1);
                        prevcommand->doio = NULL;
                        /* --- */
                        INSERTIO(&command->doio,
                               command,
                               sIObufIn, 0, 1, 0);
                        INSERTIO(&command->undoio,
                               command,
                               sIObufFree, 0, 0, 0);
                        /*
                         * ... else we're connecting two
                         * list-valued functions, which is ok.
                         */
                        cmdname = NULL;
                        if (prevcommand->sfdp)
                          cmdname = prevcommand->sfdp->name;
                        if (prevcommand->shcmdp)
                          cmdname = prevcommand->shcmdp->name;
                        runcommand(prevcommand, caller,
                                 ignore_level > commandIndex ?
                                 retcodep : NULL, cmdname);
                        command->rval = prevcommand->rval;
                        if (command->prev == prevcommand)
                              command->prev = NULL;
                        free((char *)prevcommand);
                        prevcommand = NULL;
                        /* X:shouldn't prevcommand be stacked?*/
                  }
                  break;
            case sLoopExit:
                  if (nloop >= 0)
                        --nloop;
                  break;
            case sLocalVariable:
                  /* create the variable in the current scope */
                  d = NIL;
                  {
                    int slen = strlen(arg1);
#if 0
                    /* XXX: If we can be sure of input
                       (arg1) being on stabile storage..
                       Command-line interpreter (shell)
                       is not stabile storage, but script
                       interpreter is! */
                    tmp = conststring(arg1,slen);
#else
                    tmp = newstring(dupnstr(arg1,slen),slen);
#endif
                  }
                  cdr(d) = caar(envarlist);
                  cdr(tmp) = d;
                  caar(envarlist) = tmp;
                  if (isset('I'))
                        grindef("Scopes = ", envarlist);
                  break;
            case sScopePush:
                  d = NIL;
                  s_push(d, envarlist);
                  /* fvcache.namesymbol = 0; */
                  break;
            case sScopePop:
                  d = car(envarlist);
                  car(envarlist) = cdar(envarlist);
                  cdr(d) = NULL;
                  /*s_free_tree(d);*/
                  /* fvcache.namesymbol = 0; */
                  break;
            case sDollarExpand:
                  dollar = 1;
                  break;
            case sBufferQuote:
                  quote = 1;
                  break;
            case sBackground:
                  /*
                   * 1. tell child and pipeline commands to background.
                   * 2. find place to stash pid of this command for pgrp.
                   * 3. call hook when this command finishes to print
                   *    pids of all commands and job number.
                   */
                  command->pgrpp = &command->pgrp;
                  break;
            case sPrintAndExit:
                  if (command->buffer == NULL)
                        d = conststring(uBLANK,0);
                  else if (cdr(command->buffer))
                        d = s_catstring(command->buffer);
                  else
                        d = command->buffer;
                  fprintf(stderr, "%s: %s\n", progname,
                              *(d->string) == '\0' ?
                              "parameter null or not set"
                              : d->string);
                  if (!isset('i'))
                        trapexit(1);
                  break;
#ifdef      MAILER
/* String RegExpressions */

            case sSiftPush:
                  v_record = 1;
                  if (nsift >= 0)
                        sift[nsift].program = re;
                  if (++nsift >= (sizeof sift / sizeof sift[0])) {
                        fprintf(stderr,"%s: sSiftPush more than %d times allowed by the interpret.c code\n",
                              progname, nsift-1);
                        abort(); /* Too deep SIFTs! */
                  }
                  sift[nsift].kind  = 0;
                  sift[nsift].tlist = NULL; /* the other sift type */
                  sift[nsift].str = NULL;   /* this sift type */
                  sift[nsift].label = pc+1 - code;
                  sift[nsift].subexps = NULL;
                  sift[nsift].count = 9999; /* Cut eternal loops */
                  v_accessed = NULL;
                  break;
            case sSiftBody:
#ifndef DEQUOTE_STICKY
                  if (sift[nsift].str)
                         free((void*)sift[nsift].str);
#endif
                  sift[nsift].str = NULL;
#ifdef  DEBUG
                  if (D_compare) {
                    fprintf(stderr,
                          "%*ssSiftBody '", 4*funclevel, " ");
                    if (command->buffer != NULL)
                      s_grind(command->buffer, stderr);
                    else
                      fprintf(stderr, "(nil)");
                    fprintf(stderr,"'\n");
      }
#endif  /* DEBUG */

                  if (command->buffer != NULL) {
                        if (cdr(command->buffer))
                              d = s_catstring(command->buffer);
                        else
                              d = command->buffer;
                        if (STRING(d)) {
                              sift[nsift].str = dequote(d->cstring, d->slen);
                        }
                  }
                  sift[nsift].accessed = v_accessed;  /* nop 2nd time */
                  if (v_record == 0)      /* we've been here before */
                        pc = code + loop[nloop].cont - 1;
                  else
                        v_record = 0;
                  v_changed = 0;
                  break;
            case sSiftCompileRegexp:
                  if (argi1) {
                        re = cdp->rearray[argi1-1];
#if 0
std_printf("found %x at %d\n", re, argi1-1);
#endif
                        break;
                  }
                  re = NULL;
                  if (command->buffer != NULL) {
                        if (cdr(command->buffer)) {
                              /* stickytmp = stickymem;
                                 stickymem = MEM_PERM; */
                              d = s_catstring(command->buffer);
                              /* stickymem = stickytmp; */
                        } else
                              d = command->buffer;
                        if (STRING(d))
                              re = reg_comp(d->string, d->slen);
                  }
                  if (re == NULL)
                        break;
                  if (cdp->rearray_size == 0) {
#define     RECLICK 25
                        cdp->rearray_size = RECLICK;
                        cdp->rearray = (regexp **)
                              emalloc(RECLICK*sizeof(regexp *));
                  } else if (cdp->rearray_idx >= cdp->rearray_size-2) {
                        /* 1 spare */
                        cdp->rearray_size *= 2;
                        cdp->rearray =
                              (regexp **)erealloc((char*)cdp->rearray,
                                              cdp->rearray_size *
                                              sizeof(regexp *));
                  }
                  cdp->rearray[++cdp->rearray_idx] = re;
#if 0
std_printf("set %x at %d\n", re, cdp->rearray_idx);
#endif

                  /* NB! we are writing into the table */
                  ++cdp->rearray_idx;
                  *(char*)(pc-3) = (cdp->rearray_idx >> 24) & 0xff;
                  *(char*)(pc-2) = (cdp->rearray_idx >> 16) & 0xff;
                  *(char*)(pc-1) = (cdp->rearray_idx >>  8) & 0xff;
                  *(char*)(pc  ) =  cdp->rearray_idx        & 0xff;
                  --cdp->rearray_idx;
                  break;
            case sSiftReevaluate:
                  if (v_changed)
                        /* jump to sift expression evaluation
                           followed by sSiftBody */
                        pc = sift[nsift].label - 1 + code;
                  break;
            case sSiftPop:
#ifndef DEQUOTE_STICKY
                  if (sift[nsift].str)
                         free((void*)sift[nsift].str);
#endif
                  sift[nsift].str = NULL;
                  for (v_accessed = sift[nsift].accessed;
                       v_accessed != NULL;
                       v_accessed = sift[nsift].accessed) {
                        sift[nsift].accessed = v_accessed->next;
                        free((char *)v_accessed);
                  }
                  --nsift;
                  if (nsift >= 0) {
                        v_accessed = sift[nsift].accessed;
                        re = sift[nsift].program;
                  }
                  break;
            case sSiftBufferAppend:
                  if (sift[nsift].kind == 0) { /* StringSift.. */
                    if (arg1 == NULL || re == NULL ||
                        !isdigit((*arg1)&0xFF))
                      break;
                    if (nsift > 0 && sift[nsift].subexps == NULL)
                      setsubexps(&sift[nsift-1].subexps, re);
                    else
                      setsubexps(&sift[nsift].subexps, re);
                    arg1 = regsub(re, atoi(arg1));
                    if (arg1 != NULL) {
                      int slen = strlen(arg1);
#if 0
                      /* XXX: If we can be sure of input
                         (arg1) being on stabile storage..
                         Command-line interpreter (shell)
                         is not stabile storage, but script
                         interpreter is! */
                      tmp = conststring(arg1,slen);
#else
                      tmp = newstring(dupnstr(arg1,slen),slen);
#endif
                      tmp->flags |= QUOTEDSTRING;

                      /* cdr(tmp) = command->buffer; */
                      *command->bufferp = tmp;
                      command->bufferp = &cdr(tmp);
                    }
                  } else { /* TokenSift */
                    char *s;
                    if (arg1 == NULL || tre == NULL ||
                        !isdigit((*arg1)&0xFF))
                      break;
                    if (nsift > 0 && sift[nsift].subexps == NULL)
                      tsetsubexps(&sift[nsift-1].subexps, tre);
                    else
                      tsetsubexps(&sift[nsift].subexps, tre);
                    s = tregsub(tre, atoi(arg1));
                    if (s != NULL) {
                      int slen = strlen(s);
                      tmp = newstring(s,slen);
                      tmp->flags |= QUOTEDSTRING;
                      /* cdr(tmp) = command->buffer; */
                      *command->bufferp = tmp;
                      command->bufferp = &cdr(tmp);
                    }
                  }
                  break;
            case sJumpIfRegmatch:
                  stickytmp = stickymem;
#ifndef DEQUOTE_STICKY
                  stickymem = MEM_PERM;
                  if (sift[nsift].str == NULL)
                        sift[nsift].str = strdup("");
#else
                  stickymem = MEM_SHCMD;
                  if (sift[nsift].str == NULL)
                        sift[nsift].str = strsave("");
#endif
                  stickymem = stickytmp;
                  setsubexps(&sift[nsift].subexps, re);
                  if ((sift[nsift].count >= 0) &&
                      !reg_exec(re, sift[nsift].str))
                        pc = code + argi1 - 1;
                  sift[nsift].count -= 1;
                  break;

/* Token RegExpressions: */

            case sTSiftPush:
                  v_record = 1;
                  if (nsift >= 0)
                        sift[nsift].program = (regexp *)tre;
                  if (++nsift >= (sizeof sift / sizeof sift[0])) {
                        fprintf(stderr,"%s: sTSiftPush more than %d times allowed by the interpret.c code\n",
                              progname, nsift-1);
                        abort(); /* Too deep TSIFT! */
                  }
                  sift[nsift].kind  = 1;
                  sift[nsift].tlist = NULL;
                  sift[nsift].str = NULL; /* the other branch */
                  sift[nsift].label = pc+1 - code;
                  sift[nsift].subexps = NULL;
                  sift[nsift].count = 9999; /* Cut eternal loops */
                  v_accessed = NULL;
                  break;
            case sTSiftBody:
                  /* we don't *need* to free tokens because they are
                     allocated off our MEM_SHCMD memory stack */
                  if (sift[nsift].tlist)
                        freeTokens(sift[nsift].tlist, MEM_SHCMD);
                  sift[nsift].tlist = NULL;
                  if (command->buffer != NULL) {
                        if (cdr(command->buffer))
                              d = s_catstring(command->buffer);
                        else
                              d = command->buffer;
                        if (STRING(d)) {
                              arg1 = (const char *) d->string;
                              sift[nsift].tlist = tscanstring(arg1);
                        }
                  }
                  sift[nsift].accessed = v_accessed;  /* nop 2nd time */
                  if (v_record == 0)      /* we've been here before */
                        pc = code + loop[nloop].cont - 1;
                  else
                        v_record = 0;
                  v_changed = 0;
                  break;
            case sTSiftCompileRegexp:
                  if (argi1) {
                    tre = cdp->trearray[argi1-1];
#if 0
std_printf("found %x at %d\n", tre, argi1-1);
#endif
                    break;
                  }
                  tre = NULL;
                  if (command->buffer != NULL) {
                    if (cdr(command->buffer)) {
                      d = s_catstring(command->buffer);
                    } else
                      d = command->buffer;
                    if (STRING(d))
                      tre = tregcomp(d->string);
                  }
                  if (tre == NULL)
                    break;
                  if (cdp->trearray_size == 0) {
                    cdp->trearray_size = RECLICK;
                    cdp->trearray = (tregexp **)
                      emalloc(RECLICK*sizeof(tregexp *));
                  } else if (cdp->trearray_idx >= cdp->trearray_size-2) {
                    /* 1 spare */
                    cdp->trearray_size *= 2;
                    cdp->trearray =
                      (tregexp **)erealloc((char*)cdp->trearray,
                                     cdp->trearray_size *
                                     sizeof(tregexp *));
                  }
                  cdp->trearray[++cdp->trearray_idx] = tre;
#if 0
std_printf("set %x at %d\n", tre, cdp->trearray_idx);
#endif

                  /* NB! we are writing into the table */
                  ++cdp->trearray_idx;
                  *(char*)(pc-3) = (cdp->trearray_idx >> 24) & 0xff;
                  *(char*)(pc-2) = (cdp->trearray_idx >> 16) & 0xff;
                  *(char*)(pc-1) = (cdp->trearray_idx >>  8) & 0xff;
                  *(char*)(pc  ) =  cdp->trearray_idx        & 0xff;
                  --cdp->trearray_idx;
                  break;
            case sTSiftReevaluate:
                  if (v_changed)
                        /* jump to sift expression evaluation
                           followed by sTSiftBody */
                        pc = sift[nsift].label-1 + code;
                  break;
            case sTSiftPop:
                  /* see comment above about freeing tokens */
                  if (sift[nsift].tlist)
                        freeTokens(sift[nsift].tlist, MEM_SHCMD);
                  sift[nsift].tlist = NULL;
                  for (v_accessed = sift[nsift].accessed;
                       v_accessed != NULL;
                       v_accessed = sift[nsift].accessed) {
                        sift[nsift].accessed = v_accessed->next;
                        free((char *)v_accessed);
                  }
                  --nsift;
                  if (nsift >= 0) {
                        v_accessed = sift[nsift].accessed;
                        tre = (tregexp *)sift[nsift].program;
                  }
                  break;
#if 0 /* wrong place.. */
            case sTSiftBufferAppend:
                  if (arg1 == NULL || tre == NULL || !isdigit(*arg1))
                        break;
                  if (nsift > 0 && sift[nsift].subexps == NULL)
                        tsetsubexps(&sift[nsift-1].subexps, tre);
                  else
                        tsetsubexps(&sift[nsift].subexps, tre);
                  if ((arg1 = tregsub(tre,atoi(arg1))) != NULL) {
                        int slen = strlen(arg1);
#if 0
                        tmp = coststring(arg1, slen);
#else
                        tmp = newstring(dupnstr(arg1,slen),slen);
#endif
                        tmp->flags |= QUOTEDSTRING;
                        /* cdr(tmp) = command->buffer; */
                        *command->bufferp = tmp;
                        command->bufferp = &cdr(tmp);
                  }
                  break;
#endif
            case sTJumpIfRegmatch:
                  if (sift[nsift].tlist == NULL)
                    sift[nsift].tlist = makeToken(uBLANK, 0);
                  tsetsubexps(&sift[nsift].subexps, tre);
                  if ((sift[nsift].count >= 0) &&
                      !tregexec(tre, sift[nsift].tlist))
                        pc = code + argi1 - 1;
                  sift[nsift].count -= 1;
                  break;

#endif      /* MAILER */
            default:
                  fprintf(stderr,
                        "Hey, you forgot to update the interpreter!\n");
                  fprintf(stderr,
                        "Illegal command token %d\n", (int)cmd);
                  if (!isset('i'))
                        exit(1);
                  break;
            }
      }
getout:
      if (dye /* I know this is misspelled */)
            trapexit(0);
      if (isset('I'))
            grindef("Vars = ", envarlist);
      /* null any loop variable values so we can free scopes below */
      while (variableIndex > -1) {
            if (varmeter) {
                  car(varmeter) = NULL;
                  varmeter->flags = 0;
            }
            if (varmchain) {
              variable = car(varmchain);
              varmeter = cadr(varmchain);
              varmchain = cddr(varmchain);
            } else
              variable = varmeter = NULL;
            if (isset('I'))
              grindef("Variable = ", variable);
            --variableIndex;
      }
      while (nsift >= 0) {
#ifndef DEQUOTE_STICKY
            if (sift[nsift].str)
              free((void*)sift[nsift].str);
#endif
            sift[nsift].str = NULL;

            /* we don't *need* to free tokens because they are
               allocated off our MEM_SHCMD memory stack */
            if (sift[nsift].tlist)
              freeTokens(sift[nsift].tlist, MEM_SHCMD);

            sift[nsift].tlist = NULL;

            for (v_accessed = sift[nsift].accessed;
                 v_accessed != NULL;
                 v_accessed = sift[nsift].accessed) {
                  sift[nsift].accessed = v_accessed->next;
                  free((char *)v_accessed);
            }
            --nsift;
      }
      while (commandIndex > 0) {
            pcommand = &commandStack[commandIndex];
            if (pcommand->undoio) {
                  RUNIO(pcommand->undoio);
                  freeio(pcommand->undoio, 1);
            }
            /* The setlevel is done below */

            /* XX: Now we free them in cases where the command
               has no return value to be cared for ! */
            if (command->rval == NULL || caller == NULL)
              setlevel(MEM_SHCMD, pcommand->memlevel);

            /* Can do without UNGCPROSTORE here, as we do
               a larger scope UNGCPRO below.. */
            UNGCPROSTORE4( commandStack[commandIndex]. );
            --commandIndex;
      }

      setlevel(MEM_SHCMD, origlevel);

#ifdef      MAILER
      /*
       * This is pretty dicey; we rely on setlevel() not changing the
       * stuff that was just freed.  We need to do this to avoid
       * malloc'ing stuff unnecessarily.  For example we usually just
       * want to access the return value in the context of the caller.
       */
      if (command->rval != NULL && caller != NULL) {
            caller->rval = command->rval;
      }
#endif

      while (margin != car(envarlist)) {
            d = car(envarlist);
            car(envarlist) = cdr(d);
            cdr(d) = NULL;
            /*s_free_tree(d);*/
      }
      stickymem = origstickymem;
#ifdef      MAILER
      --funclevel;
#endif      /* MAILER */

      commandIndex = 0; /* Should be already, in fact.. */
      UNGCPROSTORE4( commandStack[commandIndex]. );

      if (cdp->functions == NULL) {
            if (cdp->rearray != NULL) {
                  while (cdp->rearray_idx >= 0)
                        free_regexp(cdp->rearray[cdp->rearray_idx--]);
                  free((char *)cdp->rearray);
            }
            if (cdp->trearray != NULL) {
                  while (cdp->trearray_idx >= 0)
                        free((void *)cdp->trearray[cdp->trearray_idx--]);
                  free((void *)cdp->trearray);
            }
            free((void *)cdp->table);
            free((void *)cdp);
            UNGCPRO6;
            return NULL;
      }
      cdp->oktofree = 1;

      UNGCPRO6;

      return cdp;
}


/*
 * Doing an apply() outside the interpreter() is only a safe thing to do
 * when outside the interpreter (...), i.e. at interactive prompt level.
 * We want to call an arbitrary shell or builtin function with some number
 * of (string) arguments.
 */

STATIC int fapply __((struct shCmd *, conscell *));
STATIC int
fapply(shcmdp, l)
      struct shCmd *shcmdp;
      conscell *l;
{
      register conscell *ll;
      int argc = 0;
#define FARGCMAX 30
      const char *argv[FARGCMAX]; /* XX: argc never to exceed magic number */

      if (shcmdp->sptr != NULL) {
            argv[argc++] = shcmdp->name;
            for (ll = car(l); ll != NULL && argc < FARGCMAX-1; ll = cdr(ll)) {
                  if (STRING(ll))
                        argv[argc++] = ll->string;
            }
            argv[argc] = NULL;
            return (*(shcmdp->sptr))(argc, argv);
      }
      /* XX: we don't need to support this, yet */
      /* else if (shcmdp->lptr != NULL) {
            return -1;
      } */
      return -1;
}

int
lapply(fname, l)
      const char *fname;
      conscell *l;
{
      int retcode = -123456;
      struct sslfuncdef *sfdp;
      struct spblk *spl;
      struct osCmd avc;
      conscell *ll, *tmp;
      spkey_t spkey;
      GCVARS4;

#ifdef DEBUG
      if (D_functions) {
        fprintf(stderr, "%*slapply('%s', (", 4*funclevel, " ", fname);
        ll = l;
        while (ll) {
          fprintf(stderr,"'");
          s_grind(ll, stderr);
          ll = cdr(ll);
          if (ll)
            fprintf(stderr,"', ");
          else
            fprintf(stderr,"'");
        }
        fprintf(stderr, "))\n");
        fflush(stderr);
      }
#endif

      spkey = symbol_lookup(fname);
      spl = NULL;
      if (!spkey)
        return -1;
      spl = sp_lookup(spkey, spt_funclist);
      if (spl == NULL) {
            spl = sp_lookup(spkey, spt_builtins);
            if (spl == NULL)
                  return -1;
            /* Non conscell input parameters to the target
               function, no conscell creates while calling it. */
            return fapply((struct shCmd *)spl->data, l);
      }
      sfdp = (struct sslfuncdef *)spl->data;
      if (sfdp == NULL)
            return -1;
      avc = avcmd;
      ll = tmp = NULL;
      GCPRO4(ll, l, avc.argv, avc.rval);
      if (l != NULL) {
            int slen = strlen(fname);
            ll = newstring(dupnstr(fname,slen),slen);
            /* Sometimes could do without strdup(),
               but often not... :-/ */
            cdr(ll) = car(l);
            car(l) = ll;
            avc.argv = l;
      }

      interpret(sfdp->tabledesc->table,
              sfdp->eot, sfdp->pos,
              &avc, &retcode, sfdp->tabledesc);
      UNGCPRO4;
      if (return_valuep != NULL) {
            *return_valuep = avc.rval;
      }
      avc.rval = NULL;
      return retcode;
}

int
apply(argc, argv)
      int argc;
      const char *argv[];
{
      conscell *args = NULL;
      int rc;
      GCVARS1;

      /* if argc == 0, don't change avc.argv even if there are arguments */
      if (argc > 1)
        args = s_listify(argc-1, &argv[1]);
      GCPRO1(args);
      rc = lapply(argv[0], args);
      UNGCPRO1;
      return rc;
}


/*
 * A cheap way of calling (e.g.) prompt-generating functions without
 * having to set up argv lists and such.  Same restrictions as apply().
 */

int
funcall(fname)
      const char *fname;
{
      const char *av[1];

      av[0] = fname;
      return apply(0, &av[0]);
}

Generated by  Doxygen 1.6.0   Back to index