#error "Do not even bother, ash will not run on uClinux"
#endif
-#if DEBUG
-#define TRACE(param) trace param
-#define TRACEV(param) tracev param
-#else
-#define TRACE(param)
-#define TRACEV(param)
-#endif
-
-#ifdef __GLIBC__
-/* glibc sucks */
-static int *dash_errno;
-#undef errno
-#define errno (*dash_errno)
-#endif
-
-
-#if ENABLE_ASH_ALIAS
-#define ALIASINUSE 1
-#define ALIASDEAD 2
-struct alias {
- struct alias *next;
- char *name;
- char *val;
- int flag;
-};
-static int aliascmd(int, char **);
-static int unaliascmd(int, char **);
-static void printalias(const struct alias *);
-#endif
-
/* ============ Shell options */
/* ============ Misc data */
+#ifdef __GLIBC__
+/* glibc sucks */
+static int *dash_errno;
+#undef errno
+#define errno (*dash_errno)
+#endif
+
static char nullstr[1]; /* zero length string */
static const char homestr[] = "HOME";
static const char snlfmt[] = "%s\n";
}
#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
-static void int_on(void)
+static void
+int_on(void)
{
if (--suppressint == 0 && intpending) {
raise_interrupt();
}
}
#define INT_ON int_on()
-static void force_int_on(void)
+static void
+force_int_on(void)
{
suppressint = 0;
if (intpending)
}
-/* ============ Parser data
- *
- * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
- */
+/* ============ Parsing structures */
+#define NCMD 0
+#define NPIPE 1
+#define NREDIR 2
+#define NBACKGND 3
+#define NSUBSHELL 4
+#define NAND 5
+#define NOR 6
+#define NSEMI 7
+#define NIF 8
+#define NWHILE 9
+#define NUNTIL 10
+#define NFOR 11
+#define NCASE 12
+#define NCLIST 13
+#define NDEFUN 14
+#define NARG 15
+#define NTO 16
+#define NCLOBBER 17
+#define NFROM 18
+#define NFROMTO 19
+#define NAPPEND 20
+#define NTOFD 21
+#define NFROMFD 22
+#define NHERE 23
+#define NXHERE 24
+#define NNOT 25
-struct strlist {
- struct strlist *next;
+union node;
+
+struct ncmd {
+ int type;
+ union node *assign;
+ union node *args;
+ union node *redirect;
+};
+
+struct npipe {
+ int type;
+ int backgnd;
+ struct nodelist *cmdlist;
+};
+
+struct nredir {
+ int type;
+ union node *n;
+ union node *redirect;
+};
+
+struct nbinary {
+ int type;
+ union node *ch1;
+ union node *ch2;
+};
+
+struct nif {
+ int type;
+ union node *test;
+ union node *ifpart;
+ union node *elsepart;
+};
+
+struct nfor {
+ int type;
+ union node *args;
+ union node *body;
+ char *var;
+};
+
+struct ncase {
+ int type;
+ union node *expr;
+ union node *cases;
+};
+
+struct nclist {
+ int type;
+ union node *next;
+ union node *pattern;
+ union node *body;
+};
+
+struct narg {
+ int type;
+ union node *next;
char *text;
+ struct nodelist *backquote;
};
-struct strpush {
- struct strpush *prev; /* preceding string on stack */
- char *prevstring;
- int prevnleft;
-#if ENABLE_ASH_ALIAS
- struct alias *ap; /* if push was associated with an alias */
-#endif
- char *string; /* remember the string since it may change */
+struct nfile {
+ int type;
+ union node *next;
+ int fd;
+ union node *fname;
+ char *expfname;
};
-struct parsefile {
- struct parsefile *prev; /* preceding file on stack */
- int linno; /* current line */
- int fd; /* file descriptor (or -1 if string) */
- int nleft; /* number of chars left in this line */
- int lleft; /* number of chars left in this buffer */
- char *nextc; /* next char in buffer */
- char *buf; /* input buffer */
- struct strpush *strpush; /* for pushing strings at this level */
- struct strpush basestrpush; /* so pushing one is fast */
+struct ndup {
+ int type;
+ union node *next;
+ int fd;
+ int dupfd;
+ union node *vname;
};
-static struct parsefile basepf; /* top level input file */
-static struct parsefile *parsefile = &basepf; /* current input file */
-static int startlinno; /* line # where last token started */
-static char *commandname; /* currently executing command */
-static struct strlist *cmdenviron; /* environment for builtin command */
-static int exitstatus; /* exit status of last command */
-static int back_exitstatus; /* exit status of backquoted command */
+struct nhere {
+ int type;
+ union node *next;
+ int fd;
+ union node *doc;
+};
+struct nnot {
+ int type;
+ union node *com;
+};
-/* ============ Message printing */
+union node {
+ int type;
+ struct ncmd ncmd;
+ struct npipe npipe;
+ struct nredir nredir;
+ struct nbinary nbinary;
+ struct nif nif;
+ struct nfor nfor;
+ struct ncase ncase;
+ struct nclist nclist;
+ struct narg narg;
+ struct nfile nfile;
+ struct ndup ndup;
+ struct nhere nhere;
+ struct nnot nnot;
+};
-static void
-ash_vmsg(const char *msg, va_list ap)
-{
- fprintf(stderr, "%s: ", arg0);
- if (commandname) {
- const char *fmt = (!iflag || parsefile->fd) ?
- "%s: %d: " : "%s: ";
- fprintf(stderr, fmt, commandname, startlinno);
- }
- vfprintf(stderr, msg, ap);
- outcslow('\n', stderr);
-}
+struct nodelist {
+ struct nodelist *next;
+ union node *n;
+};
+
+struct funcnode {
+ int count;
+ union node n;
+};
+
+
+/* ============ Debugging output */
-/*
- * Exverror is called to raise the error exception. If the second argument
- * is not NULL then error prints an error message using printf style
- * formatting. It then raises the error exception.
- */
-static void ash_vmsg_and_raise(int, const char *, va_list) ATTRIBUTE_NORETURN;
-static void
-ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
-{
#if DEBUG
- if (msg) {
- TRACE(("ash_vmsg_and_raise(%d, \"", cond));
- TRACEV((msg, ap));
- TRACE(("\") pid=%d\n", getpid()));
- } else
- TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
- if (msg)
-#endif
- ash_vmsg(msg, ap);
- flush_stdout_stderr();
- raise_exception(cond);
- /* NOTREACHED */
-}
+static FILE *tracefile;
-static void ash_msg_and_raise_error(const char *, ...) ATTRIBUTE_NORETURN;
static void
-ash_msg_and_raise_error(const char *msg, ...)
+trace_printf(const char *fmt, ...)
{
- va_list ap;
+ va_list va;
- va_start(ap, msg);
- ash_vmsg_and_raise(EXERROR, msg, ap);
- /* NOTREACHED */
- va_end(ap);
+ if (debug != 1)
+ return;
+ va_start(va, fmt);
+ vfprintf(tracefile, fmt, va);
+ va_end(va);
}
-static void ash_msg_and_raise(int, const char *, ...) ATTRIBUTE_NORETURN;
static void
-ash_msg_and_raise(int cond, const char *msg, ...)
+trace_vprintf(const char *fmt, va_list va)
{
- va_list ap;
+ if (debug != 1)
+ return;
+ vfprintf(tracefile, fmt, va);
+}
- va_start(ap, msg);
- ash_vmsg_and_raise(cond, msg, ap);
- /* NOTREACHED */
- va_end(ap);
-}
-
-/*
- * error/warning routines for external builtins
- */
static void
-ash_msg(const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- ash_vmsg(fmt, ap);
- va_end(ap);
-}
-
-/*
- * Return a string describing an error. The returned string may be a
- * pointer to a static buffer that will be overwritten on the next call.
- * Action describes the operation that got the error.
- */
-static const char *
-errmsg(int e, const char *em)
-{
- if (e == ENOENT || e == ENOTDIR) {
- return em;
- }
- return strerror(e);
-}
-
-
-/* ============ Memory allocation */
-
-/*
- * It appears that grabstackstr() will barf with such alignments
- * because stalloc() will return a string allocated in a new stackblock.
- */
-#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
-enum {
- /* Most machines require the value returned from malloc to be aligned
- * in some way. The following macro will get this right
- * on many machines. */
- SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
- /* Minimum size of a block */
- MINSIZE = SHELL_ALIGN(504),
-};
-
-struct stack_block {
- struct stack_block *prev;
- char space[MINSIZE];
-};
-
-struct stackmark {
- struct stack_block *stackp;
- char *stacknxt;
- size_t stacknleft;
- struct stackmark *marknext;
-};
-
-static struct stack_block stackbase;
-static struct stack_block *stackp = &stackbase;
-static struct stackmark *markp;
-static char *stacknxt = stackbase.space;
-static size_t stacknleft = MINSIZE;
-static char *sstrend = stackbase.space + MINSIZE;
-static int herefd = -1;
-
-#define stackblock() ((void *)stacknxt)
-#define stackblocksize() stacknleft
-
-static void *
-ckrealloc(void * p, size_t nbytes)
-{
- p = realloc(p, nbytes);
- if (!p)
- ash_msg_and_raise_error(bb_msg_memory_exhausted);
- return p;
-}
-
-static void *
-ckmalloc(size_t nbytes)
-{
- return ckrealloc(NULL, nbytes);
-}
-
-/*
- * Make a copy of a string in safe storage.
- */
-static char *
-ckstrdup(const char *s)
+trace_puts(const char *s)
{
- char *p = strdup(s);
- if (!p)
- ash_msg_and_raise_error(bb_msg_memory_exhausted);
- return p;
+ if (debug != 1)
+ return;
+ fputs(s, tracefile);
}
-/*
- * Parse trees for commands are allocated in lifo order, so we use a stack
- * to make this more efficient, and also to avoid all sorts of exception
- * handling code to handle interrupts in the middle of a parse.
- *
- * The size 504 was chosen because the Ultrix malloc handles that size
- * well.
- */
-static void *
-stalloc(size_t nbytes)
+static void
+trace_puts_quoted(char *s)
{
char *p;
- size_t aligned;
-
- aligned = SHELL_ALIGN(nbytes);
- if (aligned > stacknleft) {
- size_t len;
- size_t blocksize;
- struct stack_block *sp;
+ char c;
- blocksize = aligned;
- if (blocksize < MINSIZE)
- blocksize = MINSIZE;
- len = sizeof(struct stack_block) - MINSIZE + blocksize;
- if (len < blocksize)
- ash_msg_and_raise_error(bb_msg_memory_exhausted);
- INT_OFF;
- sp = ckmalloc(len);
- sp->prev = stackp;
- stacknxt = sp->space;
- stacknleft = blocksize;
- sstrend = stacknxt + blocksize;
- stackp = sp;
- INT_ON;
+ if (debug != 1)
+ return;
+ putc('"', tracefile);
+ for (p = s; *p; p++) {
+ switch (*p) {
+ case '\n': c = 'n'; goto backslash;
+ case '\t': c = 't'; goto backslash;
+ case '\r': c = 'r'; goto backslash;
+ case '"': c = '"'; goto backslash;
+ case '\\': c = '\\'; goto backslash;
+ case CTLESC: c = 'e'; goto backslash;
+ case CTLVAR: c = 'v'; goto backslash;
+ case CTLVAR+CTLQUOTE: c = 'V'; goto backslash;
+ case CTLBACKQ: c = 'q'; goto backslash;
+ case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash;
+ backslash:
+ putc('\\', tracefile);
+ putc(c, tracefile);
+ break;
+ default:
+ if (*p >= ' ' && *p <= '~')
+ putc(*p, tracefile);
+ else {
+ putc('\\', tracefile);
+ putc(*p >> 6 & 03, tracefile);
+ putc(*p >> 3 & 07, tracefile);
+ putc(*p & 07, tracefile);
+ }
+ break;
+ }
}
- p = stacknxt;
- stacknxt += aligned;
- stacknleft -= aligned;
- return p;
+ putc('"', tracefile);
}
static void
-stunalloc(void *p)
+trace_puts_args(char **ap)
{
-#if DEBUG
- if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
- write(2, "stunalloc\n", 10);
- abort();
+ if (debug != 1)
+ return;
+ if (!*ap)
+ return;
+ while (1) {
+ trace_puts_quoted(*ap);
+ if (!*++ap) {
+ putc('\n', tracefile);
+ break;
+ }
+ putc(' ', tracefile);
}
-#endif
- stacknleft += stacknxt - (char *)p;
- stacknxt = p;
}
-/*
- * Like strdup but works with the ash stack.
- */
-static char *
-ststrdup(const char *p)
+static void
+opentrace(void)
{
- size_t len = strlen(p) + 1;
- return memcpy(stalloc(len), p, len);
+ char s[100];
+#ifdef O_APPEND
+ int flags;
+#endif
+
+ if (debug != 1) {
+ if (tracefile)
+ fflush(tracefile);
+ /* leave open because libedit might be using it */
+ return;
+ }
+ strcpy(s, "./trace");
+ if (tracefile) {
+ if (!freopen(s, "a", tracefile)) {
+ fprintf(stderr, "Can't re-open %s\n", s);
+ debug = 0;
+ return;
+ }
+ } else {
+ tracefile = fopen(s, "a");
+ if (tracefile == NULL) {
+ fprintf(stderr, "Can't open %s\n", s);
+ debug = 0;
+ return;
+ }
+ }
+#ifdef O_APPEND
+ flags = fcntl(fileno(tracefile), F_GETFL, 0);
+ if (flags >= 0)
+ fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+#endif
+ setlinebuf(tracefile);
+ fputs("\nTracing started.\n", tracefile);
}
static void
-setstackmark(struct stackmark *mark)
+indent(int amount, char *pfx, FILE *fp)
{
- mark->stackp = stackp;
- mark->stacknxt = stacknxt;
- mark->stacknleft = stacknleft;
- mark->marknext = markp;
- markp = mark;
+ int i;
+
+ for (i = 0; i < amount; i++) {
+ if (pfx && i == amount - 1)
+ fputs(pfx, fp);
+ putc('\t', fp);
+ }
}
+/* little circular references here... */
+static void shtree(union node *n, int ind, char *pfx, FILE *fp);
+
static void
-popstackmark(struct stackmark *mark)
+sharg(union node *arg, FILE *fp)
{
- struct stack_block *sp;
+ char *p;
+ struct nodelist *bqlist;
+ int subtype;
- INT_OFF;
- markp = mark->marknext;
- while (stackp != mark->stackp) {
- sp = stackp;
- stackp = sp->prev;
- free(sp);
+ if (arg->type != NARG) {
+ out1fmt("<node type %d>\n", arg->type);
+ abort();
}
- stacknxt = mark->stacknxt;
- stacknleft = mark->stacknleft;
- sstrend = mark->stacknxt + mark->stacknleft;
- INT_ON;
-}
-
-/*
- * When the parser reads in a string, it wants to stick the string on the
- * stack and only adjust the stack pointer when it knows how big the
- * string is. Stackblock (defined in stack.h) returns a pointer to a block
- * of space on top of the stack and stackblocklen returns the length of
- * this block. Growstackblock will grow this space by at least one byte,
- * possibly moving it (like realloc). Grabstackblock actually allocates the
- * part of the block that has been used.
- */
-static void
-growstackblock(void)
-{
- size_t newlen;
-
- newlen = stacknleft * 2;
- if (newlen < stacknleft)
- ash_msg_and_raise_error(bb_msg_memory_exhausted);
- if (newlen < 128)
- newlen += 128;
+ bqlist = arg->narg.backquote;
+ for (p = arg->narg.text; *p; p++) {
+ switch (*p) {
+ case CTLESC:
+ putc(*++p, fp);
+ break;
+ case CTLVAR:
+ putc('$', fp);
+ putc('{', fp);
+ subtype = *++p;
+ if (subtype == VSLENGTH)
+ putc('#', fp);
- if (stacknxt == stackp->space && stackp != &stackbase) {
- struct stack_block *oldstackp;
- struct stackmark *xmark;
- struct stack_block *sp;
- struct stack_block *prevstackp;
- size_t grosslen;
+ while (*p != '=')
+ putc(*p++, fp);
- INT_OFF;
- oldstackp = stackp;
- sp = stackp;
- prevstackp = sp->prev;
- grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
- sp = ckrealloc(sp, grosslen);
- sp->prev = prevstackp;
- stackp = sp;
- stacknxt = sp->space;
- stacknleft = newlen;
- sstrend = sp->space + newlen;
+ if (subtype & VSNUL)
+ putc(':', fp);
- /*
- * Stack marks pointing to the start of the old block
- * must be relocated to point to the new block
- */
- xmark = markp;
- while (xmark != NULL && xmark->stackp == oldstackp) {
- xmark->stackp = stackp;
- xmark->stacknxt = stacknxt;
- xmark->stacknleft = stacknleft;
- xmark = xmark->marknext;
+ switch (subtype & VSTYPE) {
+ case VSNORMAL:
+ putc('}', fp);
+ break;
+ case VSMINUS:
+ putc('-', fp);
+ break;
+ case VSPLUS:
+ putc('+', fp);
+ break;
+ case VSQUESTION:
+ putc('?', fp);
+ break;
+ case VSASSIGN:
+ putc('=', fp);
+ break;
+ case VSTRIMLEFT:
+ putc('#', fp);
+ break;
+ case VSTRIMLEFTMAX:
+ putc('#', fp);
+ putc('#', fp);
+ break;
+ case VSTRIMRIGHT:
+ putc('%', fp);
+ break;
+ case VSTRIMRIGHTMAX:
+ putc('%', fp);
+ putc('%', fp);
+ break;
+ case VSLENGTH:
+ break;
+ default:
+ out1fmt("<subtype %d>", subtype);
+ }
+ break;
+ case CTLENDVAR:
+ putc('}', fp);
+ break;
+ case CTLBACKQ:
+ case CTLBACKQ|CTLQUOTE:
+ putc('$', fp);
+ putc('(', fp);
+ shtree(bqlist->n, -1, NULL, fp);
+ putc(')', fp);
+ break;
+ default:
+ putc(*p, fp);
+ break;
}
- INT_ON;
- } else {
- char *oldspace = stacknxt;
- int oldlen = stacknleft;
- char *p = stalloc(newlen);
-
- /* free the space we just allocated */
- stacknxt = memcpy(p, oldspace, oldlen);
- stacknleft += newlen;
}
}
static void
-grabstackblock(size_t len)
+shcmd(union node *cmd, FILE *fp)
{
- len = SHELL_ALIGN(len);
- stacknxt += len;
- stacknleft -= len;
-}
+ union node *np;
+ int first;
+ const char *s;
+ int dftfd;
-/*
- * The following routines are somewhat easier to use than the above.
- * The user declares a variable of type STACKSTR, which may be declared
- * to be a register. The macro STARTSTACKSTR initializes things. Then
- * the user uses the macro STPUTC to add characters to the string. In
- * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
- * grown as necessary. When the user is done, she can just leave the
- * string there and refer to it using stackblock(). Or she can allocate
- * the space for it using grabstackstr(). If it is necessary to allow
- * someone else to use the stack temporarily and then continue to grow
- * the string, the user should use grabstack to allocate the space, and
- * then call ungrabstr(p) to return to the previous mode of operation.
- *
- * USTPUTC is like STPUTC except that it doesn't check for overflow.
- * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
- * is space for at least one character.
- */
-static void *
-growstackstr(void)
-{
- size_t len = stackblocksize();
- if (herefd >= 0 && len >= 1024) {
- full_write(herefd, stackblock(), len);
- return stackblock();
+ first = 1;
+ for (np = cmd->ncmd.args; np; np = np->narg.next) {
+ if (! first)
+ putchar(' ');
+ sharg(np, fp);
+ first = 0;
+ }
+ for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
+ if (! first)
+ putchar(' ');
+ switch (np->nfile.type) {
+ case NTO: s = ">"; dftfd = 1; break;
+ case NCLOBBER: s = ">|"; dftfd = 1; break;
+ case NAPPEND: s = ">>"; dftfd = 1; break;
+ case NTOFD: s = ">&"; dftfd = 1; break;
+ case NFROM: s = "<"; dftfd = 0; break;
+ case NFROMFD: s = "<&"; dftfd = 0; break;
+ case NFROMTO: s = "<>"; dftfd = 0; break;
+ default: s = "*error*"; dftfd = 0; break;
+ }
+ if (np->nfile.fd != dftfd)
+ fprintf(fp, "%d", np->nfile.fd);
+ fputs(s, fp);
+ if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
+ fprintf(fp, "%d", np->ndup.dupfd);
+ } else {
+ sharg(np->nfile.fname, fp);
+ }
+ first = 0;
}
- growstackblock();
- return stackblock() + len;
}
-/*
- * Called from CHECKSTRSPACE.
- */
-static char *
-makestrspace(size_t newlen, char *p)
+static void
+shtree(union node *n, int ind, char *pfx, FILE *fp)
{
- size_t len = p - stacknxt;
- size_t size = stackblocksize();
+ struct nodelist *lp;
+ const char *s;
- for (;;) {
- size_t nleft;
+ if (n == NULL)
+ return;
- size = stackblocksize();
- nleft = size - len;
- if (nleft >= newlen)
- break;
- growstackblock();
+ indent(ind, pfx, fp);
+ switch (n->type) {
+ case NSEMI:
+ s = "; ";
+ goto binop;
+ case NAND:
+ s = " && ";
+ goto binop;
+ case NOR:
+ s = " || ";
+ binop:
+ shtree(n->nbinary.ch1, ind, NULL, fp);
+ /* if (ind < 0) */
+ fputs(s, fp);
+ shtree(n->nbinary.ch2, ind, NULL, fp);
+ break;
+ case NCMD:
+ shcmd(n, fp);
+ if (ind >= 0)
+ putc('\n', fp);
+ break;
+ case NPIPE:
+ for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
+ shcmd(lp->n, fp);
+ if (lp->next)
+ fputs(" | ", fp);
+ }
+ if (n->npipe.backgnd)
+ fputs(" &", fp);
+ if (ind >= 0)
+ putc('\n', fp);
+ break;
+ default:
+ fprintf(fp, "<node type %d>", n->type);
+ if (ind >= 0)
+ putc('\n', fp);
+ break;
}
- return stackblock() + len;
}
-static char *
-stack_nputstr(const char *s, size_t n, char *p)
+static void
+showtree(union node *n)
{
- p = makestrspace(n, p);
- p = memcpy(p, s, n) + n;
- return p;
+ trace_puts("showtree called\n");
+ shtree(n, 1, NULL, stdout);
}
-static char *
-stack_putstr(const char *s, char *p)
-{
- return stack_nputstr(s, strlen(s), p);
-}
-
-static char *
-_STPUTC(int c, char *p)
-{
- if (p == sstrend)
- p = growstackstr();
- *p++ = c;
- return p;
-}
+#define TRACE(param) trace_printf param
+#define TRACEV(param) trace_vprintf param
-#define STARTSTACKSTR(p) ((p) = stackblock())
-#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
-#define CHECKSTRSPACE(n, p) \
- ({ \
- char *q = (p); \
- size_t l = (n); \
- size_t m = sstrend - q; \
- if (l > m) \
- (p) = makestrspace(l, q); \
- 0; \
- })
-#define USTPUTC(c, p) (*p++ = (c))
-#define STACKSTRNUL(p) ((p) == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0'))
-#define STUNPUTC(p) (--p)
-#define STTOPC(p) p[-1]
-#define STADJUST(amount, p) (p += (amount))
+#else
-#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock())
-#define ungrabstackstr(s, p) stunalloc((s))
-#define stackstrend() ((void *)sstrend)
+#define TRACE(param)
+#define TRACEV(param)
+#endif /* DEBUG */
-/* ============ String helpers */
-/*
- * prefix -- see if pfx is a prefix of string.
+/* ============ Parser data
+ *
+ * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
*/
-static char *
-prefix(const char *string, const char *pfx)
+
+struct strlist {
+ struct strlist *next;
+ char *text;
+};
+
+#if ENABLE_ASH_ALIAS
+#define ALIASINUSE 1
+#define ALIASDEAD 2
+struct alias;
+static int aliascmd(int, char **);
+static int unaliascmd(int, char **);
+static void printalias(const struct alias *);
+#endif
+
+struct strpush {
+ struct strpush *prev; /* preceding string on stack */
+ char *prevstring;
+ int prevnleft;
+#if ENABLE_ASH_ALIAS
+ struct alias *ap; /* if push was associated with an alias */
+#endif
+ char *string; /* remember the string since it may change */
+};
+
+struct parsefile {
+ struct parsefile *prev; /* preceding file on stack */
+ int linno; /* current line */
+ int fd; /* file descriptor (or -1 if string) */
+ int nleft; /* number of chars left in this line */
+ int lleft; /* number of chars left in this buffer */
+ char *nextc; /* next char in buffer */
+ char *buf; /* input buffer */
+ struct strpush *strpush; /* for pushing strings at this level */
+ struct strpush basestrpush; /* so pushing one is fast */
+};
+
+static struct parsefile basepf; /* top level input file */
+static struct parsefile *parsefile = &basepf; /* current input file */
+static int startlinno; /* line # where last token started */
+static char *commandname; /* currently executing command */
+static struct strlist *cmdenviron; /* environment for builtin command */
+static int exitstatus; /* exit status of last command */
+static int back_exitstatus; /* exit status of backquoted command */
+
+
+/* ============ Message printing */
+
+static void
+ash_vmsg(const char *msg, va_list ap)
{
- while (*pfx) {
- if (*pfx++ != *string++)
- return 0;
+ fprintf(stderr, "%s: ", arg0);
+ if (commandname) {
+ const char *fmt = (!iflag || parsefile->fd) ?
+ "%s: %d: " : "%s: ";
+ fprintf(stderr, fmt, commandname, startlinno);
}
- return (char *) string;
+ vfprintf(stderr, msg, ap);
+ outcslow('\n', stderr);
}
/*
- * Check for a valid number. This should be elsewhere.
+ * Exverror is called to raise the error exception. If the second argument
+ * is not NULL then error prints an error message using printf style
+ * formatting. It then raises the error exception.
*/
-static int
-is_number(const char *p)
+static void ash_vmsg_and_raise(int, const char *, va_list) ATTRIBUTE_NORETURN;
+static void
+ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
{
- do {
- if (!isdigit(*p))
- return 0;
- } while (*++p != '\0');
- return 1;
+#if DEBUG
+ if (msg) {
+ TRACE(("ash_vmsg_and_raise(%d, \"", cond));
+ TRACEV((msg, ap));
+ TRACE(("\") pid=%d\n", getpid()));
+ } else
+ TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
+ if (msg)
+#endif
+ ash_vmsg(msg, ap);
+
+ flush_stdout_stderr();
+ raise_exception(cond);
+ /* NOTREACHED */
}
-/*
- * Convert a string of digits to an integer, printing an error message on
- * failure.
- */
-static int
-number(const char *s)
+static void ash_msg_and_raise_error(const char *, ...) ATTRIBUTE_NORETURN;
+static void
+ash_msg_and_raise_error(const char *msg, ...)
{
- if (!is_number(s))
- ash_msg_and_raise_error(illnum, s);
- return atoi(s);
+ va_list ap;
+
+ va_start(ap, msg);
+ ash_vmsg_and_raise(EXERROR, msg, ap);
+ /* NOTREACHED */
+ va_end(ap);
}
-/*
- * Produce a possibly single quoted string suitable as input to the shell.
- * The return string is allocated on the stack.
- */
-static char *
-single_quote(const char *s)
+static void ash_msg_and_raise(int, const char *, ...) ATTRIBUTE_NORETURN;
+static void
+ash_msg_and_raise(int cond, const char *msg, ...)
{
- char *p;
+ va_list ap;
- STARTSTACKSTR(p);
+ va_start(ap, msg);
+ ash_vmsg_and_raise(cond, msg, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
- do {
- char *q;
- size_t len;
+/*
+ * error/warning routines for external builtins
+ */
+static void
+ash_msg(const char *fmt, ...)
+{
+ va_list ap;
- len = strchrnul(s, '\'') - s;
+ va_start(ap, fmt);
+ ash_vmsg(fmt, ap);
+ va_end(ap);
+}
- q = p = makestrspace(len + 3, p);
+/*
+ * Return a string describing an error. The returned string may be a
+ * pointer to a static buffer that will be overwritten on the next call.
+ * Action describes the operation that got the error.
+ */
+static const char *
+errmsg(int e, const char *em)
+{
+ if (e == ENOENT || e == ENOTDIR) {
+ return em;
+ }
+ return strerror(e);
+}
- *q++ = '\'';
- q = memcpy(q, s, len) + len;
- *q++ = '\'';
- s += len;
- STADJUST(q - p, p);
+/* ============ Memory allocation */
- len = strspn(s, "'");
- if (!len)
- break;
+/*
+ * It appears that grabstackstr() will barf with such alignments
+ * because stalloc() will return a string allocated in a new stackblock.
+ */
+#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
+enum {
+ /* Most machines require the value returned from malloc to be aligned
+ * in some way. The following macro will get this right
+ * on many machines. */
+ SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
+ /* Minimum size of a block */
+ MINSIZE = SHELL_ALIGN(504),
+};
- q = p = makestrspace(len + 3, p);
+struct stack_block {
+ struct stack_block *prev;
+ char space[MINSIZE];
+};
- *q++ = '"';
- q = memcpy(q, s, len) + len;
- *q++ = '"';
- s += len;
+struct stackmark {
+ struct stack_block *stackp;
+ char *stacknxt;
+ size_t stacknleft;
+ struct stackmark *marknext;
+};
- STADJUST(q - p, p);
- } while (*s);
+static struct stack_block stackbase;
+static struct stack_block *stackp = &stackbase;
+static struct stackmark *markp;
+static char *stacknxt = stackbase.space;
+static size_t stacknleft = MINSIZE;
+static char *sstrend = stackbase.space + MINSIZE;
+static int herefd = -1;
- USTPUTC(0, p);
+#define stackblock() ((void *)stacknxt)
+#define stackblocksize() stacknleft
- return stackblock();
+static void *
+ckrealloc(void * p, size_t nbytes)
+{
+ p = realloc(p, nbytes);
+ if (!p)
+ ash_msg_and_raise_error(bb_msg_memory_exhausted);
+ return p;
}
+static void *
+ckmalloc(size_t nbytes)
+{
+ return ckrealloc(NULL, nbytes);
+}
-/* ============ ... */
-
-static char **argptr; /* argument list for builtin commands */
-static char *optionarg; /* set by nextopt (like getopt) */
-static char *optptr; /* used by nextopt */
+/*
+ * Make a copy of a string in safe storage.
+ */
+static char *
+ckstrdup(const char *s)
+{
+ char *p = strdup(s);
+ if (!p)
+ ash_msg_and_raise_error(bb_msg_memory_exhausted);
+ return p;
+}
/*
- * XXX - should get rid of. have all builtins use getopt(3). the
- * library getopt must have the BSD extension static variable "optreset"
- * otherwise it can't be used within the shell safely.
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
*
- * Standard option processing (a la getopt) for builtin routines. The
- * only argument that is passed to nextopt is the option string; the
- * other arguments are unnecessary. It return the character, or '\0' on
- * end of input.
+ * The size 504 was chosen because the Ultrix malloc handles that size
+ * well.
*/
-static int
-nextopt(const char *optstring)
+static void *
+stalloc(size_t nbytes)
{
char *p;
- const char *q;
- char c;
+ size_t aligned;
- p = optptr;
- if (p == NULL || *p == '\0') {
- p = *argptr;
- if (p == NULL || *p != '-' || *++p == '\0')
- return '\0';
- argptr++;
- if (LONE_DASH(p)) /* check for "--" */
- return '\0';
- }
- c = *p++;
- for (q = optstring; *q != c; ) {
- if (*q == '\0')
- ash_msg_and_raise_error("Illegal option -%c", c);
- if (*++q == ':')
- q++;
+ aligned = SHELL_ALIGN(nbytes);
+ if (aligned > stacknleft) {
+ size_t len;
+ size_t blocksize;
+ struct stack_block *sp;
+
+ blocksize = aligned;
+ if (blocksize < MINSIZE)
+ blocksize = MINSIZE;
+ len = sizeof(struct stack_block) - MINSIZE + blocksize;
+ if (len < blocksize)
+ ash_msg_and_raise_error(bb_msg_memory_exhausted);
+ INT_OFF;
+ sp = ckmalloc(len);
+ sp->prev = stackp;
+ stacknxt = sp->space;
+ stacknleft = blocksize;
+ sstrend = stacknxt + blocksize;
+ stackp = sp;
+ INT_ON;
}
- if (*++q == ':') {
- if (*p == '\0' && (p = *argptr++) == NULL)
- ash_msg_and_raise_error("No arg for -%c option", c);
- optionarg = p;
- p = NULL;
+ p = stacknxt;
+ stacknxt += aligned;
+ stacknleft -= aligned;
+ return p;
+}
+
+static void
+stunalloc(void *p)
+{
+#if DEBUG
+ if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
+ write(2, "stunalloc\n", 10);
+ abort();
}
- optptr = p;
- return c;
+#endif
+ stacknleft += stacknxt - (char *)p;
+ stacknxt = p;
}
+/*
+ * Like strdup but works with the ash stack.
+ */
+static char *
+ststrdup(const char *p)
+{
+ size_t len = strlen(p) + 1;
+ return memcpy(stalloc(len), p, len);
+}
-/* ============ Variables */
+static void
+setstackmark(struct stackmark *mark)
+{
+ mark->stackp = stackp;
+ mark->stacknxt = stacknxt;
+ mark->stacknleft = stacknleft;
+ mark->marknext = markp;
+ markp = mark;
+}
-/* flags */
-#define VEXPORT 0x01 /* variable is exported */
-#define VREADONLY 0x02 /* variable cannot be modified */
-#define VSTRFIXED 0x04 /* variable struct is statically allocated */
-#define VTEXTFIXED 0x08 /* text is statically allocated */
-#define VSTACK 0x10 /* text is allocated on the stack */
-#define VUNSET 0x20 /* the variable is not set */
-#define VNOFUNC 0x40 /* don't call the callback function */
-#define VNOSET 0x80 /* do not set variable - just readonly test */
-#define VNOSAVE 0x100 /* when text is on the heap before setvareq */
-#ifdef DYNAMIC_VAR
-# define VDYNAMIC 0x200 /* dynamic variable */
-# else
-# define VDYNAMIC 0
-#endif
+static void
+popstackmark(struct stackmark *mark)
+{
+ struct stack_block *sp;
-#if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value);
-static void change_lc_ctype(const char *value);
-#endif
+ INT_OFF;
+ markp = mark->marknext;
+ while (stackp != mark->stackp) {
+ sp = stackp;
+ stackp = sp->prev;
+ free(sp);
+ }
+ stacknxt = mark->stacknxt;
+ stacknleft = mark->stacknleft;
+ sstrend = mark->stacknxt + mark->stacknleft;
+ INT_ON;
+}
-static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin";
-#ifdef IFS_BROKEN
-static const char defifsvar[] = "IFS= \t\n";
-#define defifs (defifsvar + 4)
-#else
-static const char defifs[] = " \t\n";
-#endif
+/*
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is. Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block. Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc). Grabstackblock actually allocates the
+ * part of the block that has been used.
+ */
+static void
+growstackblock(void)
+{
+ size_t newlen;
-struct var {
- struct var *next; /* next entry in hash list */
- int flags; /* flags are defined above */
- const char *text; /* name=value */
- void (*func)(const char *); /* function to be called when */
- /* the variable gets set/unset */
-};
+ newlen = stacknleft * 2;
+ if (newlen < stacknleft)
+ ash_msg_and_raise_error(bb_msg_memory_exhausted);
+ if (newlen < 128)
+ newlen += 128;
-struct localvar {
- struct localvar *next; /* next local variable in list */
- struct var *vp; /* the variable that was made local */
- int flags; /* saved flags */
- const char *text; /* saved text */
-};
+ if (stacknxt == stackp->space && stackp != &stackbase) {
+ struct stack_block *oldstackp;
+ struct stackmark *xmark;
+ struct stack_block *sp;
+ struct stack_block *prevstackp;
+ size_t grosslen;
-/* Forward decls for varinit[] */
-#if ENABLE_ASH_MAIL
-static void chkmail(void);
-static void changemail(const char *);
-#endif
-static void changepath(const char *);
-#if ENABLE_ASH_GETOPTS
-static void getoptsreset(const char *);
-#endif
-#if ENABLE_ASH_RANDOM_SUPPORT
-static void change_random(const char *);
-#endif
+ INT_OFF;
+ oldstackp = stackp;
+ sp = stackp;
+ prevstackp = sp->prev;
+ grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
+ sp = ckrealloc(sp, grosslen);
+ sp->prev = prevstackp;
+ stackp = sp;
+ stacknxt = sp->space;
+ stacknleft = newlen;
+ sstrend = sp->space + newlen;
-static struct var varinit[] = {
-#ifdef IFS_BROKEN
- { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 },
-#else
- { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 },
-#endif
+ /*
+ * Stack marks pointing to the start of the old block
+ * must be relocated to point to the new block
+ */
+ xmark = markp;
+ while (xmark != NULL && xmark->stackp == oldstackp) {
+ xmark->stackp = stackp;
+ xmark->stacknxt = stacknxt;
+ xmark->stacknleft = stacknleft;
+ xmark = xmark->marknext;
+ }
+ INT_ON;
+ } else {
+ char *oldspace = stacknxt;
+ int oldlen = stacknleft;
+ char *p = stalloc(newlen);
-#if ENABLE_ASH_MAIL
- { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail },
- { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail },
-#endif
+ /* free the space we just allocated */
+ stacknxt = memcpy(p, oldspace, oldlen);
+ stacknleft += newlen;
+ }
+}
- { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath },
- { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 },
- { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 },
- { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 },
-#if ENABLE_ASH_GETOPTS
- { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset },
-#endif
-#if ENABLE_ASH_RANDOM_SUPPORT
- {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
-#endif
-#if ENABLE_LOCALE_SUPPORT
- {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all },
- {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype },
-#endif
-#if ENABLE_FEATURE_EDITING_SAVEHISTORY
- {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL },
-#endif
-};
-
-#define vifs varinit[0]
-#if ENABLE_ASH_MAIL
-#define vmail (&vifs)[1]
-#define vmpath (&vmail)[1]
-#else
-#define vmpath vifs
-#endif
-#define vpath (&vmpath)[1]
-#define vps1 (&vpath)[1]
-#define vps2 (&vps1)[1]
-#define vps4 (&vps2)[1]
-#define voptind (&vps4)[1]
-#if ENABLE_ASH_GETOPTS
-#define vrandom (&voptind)[1]
-#else
-#define vrandom (&vps4)[1]
-#endif
-#define defpath (defpathvar + 5)
+static void
+grabstackblock(size_t len)
+{
+ len = SHELL_ALIGN(len);
+ stacknxt += len;
+ stacknleft -= len;
+}
/*
- * The following macros access the values of the above variables.
- * They have to skip over the name. They return the null string
- * for unset variables.
+ * The following routines are somewhat easier to use than the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register. The macro STARTSTACKSTR initializes things. Then
+ * the user uses the macro STPUTC to add characters to the string. In
+ * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary. When the user is done, she can just leave the
+ * string there and refer to it using stackblock(). Or she can allocate
+ * the space for it using grabstackstr(). If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
*/
-#define ifsval() (vifs.text + 4)
-#define ifsset() ((vifs.flags & VUNSET) == 0)
-#define mailval() (vmail.text + 5)
-#define mpathval() (vmpath.text + 9)
-#define pathval() (vpath.text + 5)
-#define ps1val() (vps1.text + 4)
-#define ps2val() (vps2.text + 4)
-#define ps4val() (vps4.text + 4)
-#define optindval() (voptind.text + 7)
-
-#define mpathset() ((vmpath.flags & VUNSET) == 0)
-
-static struct var **hashvar(const char *);
-
-static int loopnest; /* current loop nesting level */
+static void *
+growstackstr(void)
+{
+ size_t len = stackblocksize();
+ if (herefd >= 0 && len >= 1024) {
+ full_write(herefd, stackblock(), len);
+ return stackblock();
+ }
+ growstackblock();
+ return stackblock() + len;
+}
/*
- * The parsefile structure pointed to by the global variable parsefile
- * contains information about the current file being read.
+ * Called from CHECKSTRSPACE.
*/
-struct redirtab {
- struct redirtab *next;
- int renamed[10];
- int nullredirs;
-};
+static char *
+makestrspace(size_t newlen, char *p)
+{
+ size_t len = p - stacknxt;
+ size_t size = stackblocksize();
-static struct redirtab *redirlist;
-static int nullredirs;
+ for (;;) {
+ size_t nleft;
-extern char **environ;
+ size = stackblocksize();
+ nleft = size - len;
+ if (nleft >= newlen)
+ break;
+ growstackblock();
+ }
+ return stackblock() + len;
+}
-static int preverrout_fd; /* save fd2 before print debug if xflag is set. */
+static char *
+stack_nputstr(const char *s, size_t n, char *p)
+{
+ p = makestrspace(n, p);
+ p = memcpy(p, s, n) + n;
+ return p;
+}
-struct shparam {
- int nparam; /* # of positional parameters (without $0) */
- unsigned char malloc; /* if parameter list dynamically allocated */
- char **p; /* parameter list */
-#if ENABLE_ASH_GETOPTS
- int optind; /* next parameter to be processed by getopts */
- int optoff; /* used by getopts */
-#endif
-};
+static char *
+stack_putstr(const char *s, char *p)
+{
+ return stack_nputstr(s, strlen(s), p);
+}
-static struct shparam shellparam; /* $@ current positional parameters */
+static char *
+_STPUTC(int c, char *p)
+{
+ if (p == sstrend)
+ p = growstackstr();
+ *p++ = c;
+ return p;
+}
-#define VTABSIZE 39
+#define STARTSTACKSTR(p) ((p) = stackblock())
+#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
+#define CHECKSTRSPACE(n, p) \
+ ({ \
+ char *q = (p); \
+ size_t l = (n); \
+ size_t m = sstrend - q; \
+ if (l > m) \
+ (p) = makestrspace(l, q); \
+ 0; \
+ })
+#define USTPUTC(c, p) (*p++ = (c))
+#define STACKSTRNUL(p) ((p) == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0'))
+#define STUNPUTC(p) (--p)
+#define STTOPC(p) p[-1]
+#define STADJUST(amount, p) (p += (amount))
-static struct var *vartab[VTABSIZE];
+#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock())
+#define ungrabstackstr(s, p) stunalloc((s))
+#define stackstrend() ((void *)sstrend)
-#if ENABLE_ASH_GETOPTS
-static void
-getoptsreset(const char *value)
-{
- shellparam.optind = number(value);
- shellparam.optoff = -1;
-}
-#endif
-#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
-#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
+/* ============ String helpers */
/*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
+ * prefix -- see if pfx is a prefix of string.
*/
static char *
-endofname(const char *name)
+prefix(const char *string, const char *pfx)
{
- char *p;
-
- p = (char *) name;
- if (!is_name(*p))
- return p;
- while (*++p) {
- if (!is_in_name(*p))
- break;
+ while (*pfx) {
+ if (*pfx++ != *string++)
+ return 0;
}
- return p;
+ return (char *) string;
}
/*
- * Compares two strings up to the first = or '\0'. The first
- * string must be terminated by '='; the second may be terminated by
- * either '=' or '\0'.
+ * Check for a valid number. This should be elsewhere.
*/
static int
-varcmp(const char *p, const char *q)
+is_number(const char *p)
{
- int c, d;
-
- while ((c = *p) == (d = *q)) {
- if (!c || c == '=')
- goto out;
- p++;
- q++;
- }
- if (c == '=')
- c = '\0';
- if (d == '=')
- d = '\0';
- out:
- return c - d;
+ do {
+ if (!isdigit(*p))
+ return 0;
+ } while (*++p != '\0');
+ return 1;
}
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
static int
-varequal(const char *a, const char *b)
+number(const char *s)
{
- return !varcmp(a, b);
+ if (!is_number(s))
+ ash_msg_and_raise_error(illnum, s);
+ return atoi(s);
}
/*
- * Find the appropriate entry in the hash table from the name.
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
*/
-static struct var **
-hashvar(const char *p)
+static char *
+single_quote(const char *s)
{
- unsigned hashval;
+ char *p;
- hashval = ((unsigned char) *p) << 4;
- while (*p && *p != '=')
- hashval += (unsigned char) *p++;
- return &vartab[hashval % VTABSIZE];
-}
+ STARTSTACKSTR(p);
-static int
-vpcmp(const void *a, const void *b)
-{
- return varcmp(*(const char **)a, *(const char **)b);
-}
+ do {
+ char *q;
+ size_t len;
-/*
- * This routine initializes the builtin variables.
- */
-static void
-initvar(void)
-{
- struct var *vp;
- struct var *end;
- struct var **vpp;
+ len = strchrnul(s, '\'') - s;
- /*
- * PS1 depends on uid
- */
-#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
- vps1.text = "PS1=\\w \\$ ";
-#else
- if (!geteuid())
- vps1.text = "PS1=# ";
-#endif
- vp = varinit;
- end = vp + sizeof(varinit) / sizeof(varinit[0]);
- do {
- vpp = hashvar(vp->text);
- vp->next = *vpp;
- *vpp = vp;
- } while (++vp < end);
-}
-
-static struct var **
-findvar(struct var **vpp, const char *name)
-{
- for (; *vpp; vpp = &(*vpp)->next) {
- if (varequal((*vpp)->text, name)) {
- break;
- }
- }
- return vpp;
-}
+ q = p = makestrspace(len + 3, p);
-/*
- * Find the value of a variable. Returns NULL if not set.
- */
-static char *
-lookupvar(const char *name)
-{
- struct var *v;
+ *q++ = '\'';
+ q = memcpy(q, s, len) + len;
+ *q++ = '\'';
+ s += len;
- v = *findvar(hashvar(name), name);
- if (v) {
-#ifdef DYNAMIC_VAR
- /*
- * Dynamic variables are implemented roughly the same way they are
- * in bash. Namely, they're "special" so long as they aren't unset.
- * As soon as they're unset, they're no longer dynamic, and dynamic
- * lookup will no longer happen at that point. -- PFM.
- */
- if ((v->flags & VDYNAMIC))
- (*v->func)(NULL);
-#endif
- if (!(v->flags & VUNSET))
- return strchrnul(v->text, '=') + 1;
- }
- return NULL;
-}
+ STADJUST(q - p, p);
-/*
- * Search the environment of a builtin command.
- */
-static char *
-bltinlookup(const char *name)
-{
- struct strlist *sp;
+ len = strspn(s, "'");
+ if (!len)
+ break;
- for (sp = cmdenviron; sp; sp = sp->next) {
- if (varequal(sp->text, name))
- return strchrnul(sp->text, '=') + 1;
- }
- return lookupvar(name);
-}
+ q = p = makestrspace(len + 3, p);
-/*
- * Same as setvar except that the variable and value are passed in
- * the first argument as name=value. Since the first argument will
- * be actually stored in the table, it should not be a string that
- * will go away.
- * Called with interrupts off.
- */
-static void
-setvareq(char *s, int flags)
-{
- struct var *vp, **vpp;
+ *q++ = '"';
+ q = memcpy(q, s, len) + len;
+ *q++ = '"';
+ s += len;
- vpp = hashvar(s);
- flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
- vp = *findvar(vpp, s);
- if (vp) {
- if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
- const char *n;
+ STADJUST(q - p, p);
+ } while (*s);
- if (flags & VNOSAVE)
- free(s);
- n = vp->text;
- ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
- }
+ USTPUTC(0, p);
- if (flags & VNOSET)
- return;
+ return stackblock();
+}
- if (vp->func && (flags & VNOFUNC) == 0)
- (*vp->func)(strchrnul(s, '=') + 1);
- if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
- free((char*)vp->text);
+/* ============ ... */
- flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
- } else {
- if (flags & VNOSET)
- return;
- /* not found */
- vp = ckmalloc(sizeof(*vp));
- vp->next = *vpp;
- vp->func = NULL;
- *vpp = vp;
- }
- if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
- s = ckstrdup(s);
- vp->text = s;
- vp->flags = flags;
-}
+static char **argptr; /* argument list for builtin commands */
+static char *optionarg; /* set by nextopt (like getopt) */
+static char *optptr; /* used by nextopt */
/*
- * Set the value of a variable. The flags argument is ored with the
- * flags of the variable. If val is NULL, the variable is unset.
+ * XXX - should get rid of. have all builtins use getopt(3). the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines. The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary. It return the character, or '\0' on
+ * end of input.
*/
-static void
-setvar(const char *name, const char *val, int flags)
+static int
+nextopt(const char *optstring)
{
- char *p, *q;
- size_t namelen;
- char *nameeq;
- size_t vallen;
+ char *p;
+ const char *q;
+ char c;
- q = endofname(name);
- p = strchrnul(q, '=');
- namelen = p - name;
- if (!namelen || p != q)
- ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
- vallen = 0;
- if (val == NULL) {
- flags |= VUNSET;
- } else {
- vallen = strlen(val);
+ p = optptr;
+ if (p == NULL || *p == '\0') {
+ p = *argptr;
+ if (p == NULL || *p != '-' || *++p == '\0')
+ return '\0';
+ argptr++;
+ if (LONE_DASH(p)) /* check for "--" */
+ return '\0';
}
- INT_OFF;
- nameeq = ckmalloc(namelen + vallen + 2);
- p = memcpy(nameeq, name, namelen) + namelen;
- if (val) {
- *p++ = '=';
- p = memcpy(p, val, vallen) + vallen;
+ c = *p++;
+ for (q = optstring; *q != c; ) {
+ if (*q == '\0')
+ ash_msg_and_raise_error("Illegal option -%c", c);
+ if (*++q == ':')
+ q++;
}
- *p = '\0';
- setvareq(nameeq, flags | VNOSAVE);
- INT_ON;
-}
-
-#if ENABLE_ASH_GETOPTS
-/*
- * Safe version of setvar, returns 1 on success 0 on failure.
- */
-static int
-setvarsafe(const char *name, const char *val, int flags)
-{
- int err;
- volatile int saveint;
- struct jmploc *volatile savehandler = exception_handler;
- struct jmploc jmploc;
-
- SAVE_INT(saveint);
- if (setjmp(jmploc.loc))
- err = 1;
- else {
- exception_handler = &jmploc;
- setvar(name, val, flags);
- err = 0;
+ if (*++q == ':') {
+ if (*p == '\0' && (p = *argptr++) == NULL)
+ ash_msg_and_raise_error("No arg for -%c option", c);
+ optionarg = p;
+ p = NULL;
}
- exception_handler = savehandler;
- RESTORE_INT(saveint);
- return err;
+ optptr = p;
+ return c;
}
-#endif
-/*
- * Unset the specified variable.
- */
-static int
-unsetvar(const char *s)
-{
- struct var **vpp;
- struct var *vp;
- int retval;
- vpp = findvar(hashvar(s), s);
- vp = *vpp;
- retval = 2;
- if (vp) {
- int flags = vp->flags;
+/* ============ Variables */
- retval = 1;
- if (flags & VREADONLY)
- goto out;
+/* flags */
+#define VEXPORT 0x01 /* variable is exported */
+#define VREADONLY 0x02 /* variable cannot be modified */
+#define VSTRFIXED 0x04 /* variable struct is statically allocated */
+#define VTEXTFIXED 0x08 /* text is statically allocated */
+#define VSTACK 0x10 /* text is allocated on the stack */
+#define VUNSET 0x20 /* the variable is not set */
+#define VNOFUNC 0x40 /* don't call the callback function */
+#define VNOSET 0x80 /* do not set variable - just readonly test */
+#define VNOSAVE 0x100 /* when text is on the heap before setvareq */
#ifdef DYNAMIC_VAR
- vp->flags &= ~VDYNAMIC;
+# define VDYNAMIC 0x200 /* dynamic variable */
+# else
+# define VDYNAMIC 0
#endif
- if (flags & VUNSET)
- goto ok;
- if ((flags & VSTRFIXED) == 0) {
- INT_OFF;
- if ((flags & (VTEXTFIXED|VSTACK)) == 0)
- free((char*)vp->text);
- *vpp = vp->next;
- free(vp);
- INT_ON;
- } else {
- setvar(s, 0, 0);
- vp->flags &= ~VEXPORT;
- }
- ok:
- retval = 0;
- }
- out:
- return retval;
-}
-/*
- * Process a linked list of variable assignments.
- */
-static void
-listsetvar(struct strlist *list_set_var, int flags)
-{
- struct strlist *lp = list_set_var;
+#if ENABLE_LOCALE_SUPPORT
+static void change_lc_all(const char *value);
+static void change_lc_ctype(const char *value);
+#endif
- if (!lp)
- return;
- INT_OFF;
- do {
- setvareq(lp->text, flags);
- lp = lp->next;
- } while (lp);
- INT_ON;
-}
+static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin";
+#ifdef IFS_BROKEN
+static const char defifsvar[] = "IFS= \t\n";
+#define defifs (defifsvar + 4)
+#else
+static const char defifs[] = " \t\n";
+#endif
+
+struct var {
+ struct var *next; /* next entry in hash list */
+ int flags; /* flags are defined above */
+ const char *text; /* name=value */
+ void (*func)(const char *); /* function to be called when */
+ /* the variable gets set/unset */
+};
+
+struct localvar {
+ struct localvar *next; /* next local variable in list */
+ struct var *vp; /* the variable that was made local */
+ int flags; /* saved flags */
+ const char *text; /* saved text */
+};
+
+/* Forward decls for varinit[] */
+#if ENABLE_ASH_MAIL
+static void chkmail(void);
+static void changemail(const char *);
+#endif
+static void changepath(const char *);
+#if ENABLE_ASH_GETOPTS
+static void getoptsreset(const char *);
+#endif
+#if ENABLE_ASH_RANDOM_SUPPORT
+static void change_random(const char *);
+#endif
+
+static struct var varinit[] = {
+#ifdef IFS_BROKEN
+ { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 },
+#else
+ { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 },
+#endif
+
+#if ENABLE_ASH_MAIL
+ { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail },
+ { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail },
+#endif
+
+ { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath },
+ { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 },
+ { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 },
+ { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 },
+#if ENABLE_ASH_GETOPTS
+ { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset },
+#endif
+#if ENABLE_ASH_RANDOM_SUPPORT
+ {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
+#endif
+#if ENABLE_LOCALE_SUPPORT
+ {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all },
+ {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype },
+#endif
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+ {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL },
+#endif
+};
+
+#define vifs varinit[0]
+#if ENABLE_ASH_MAIL
+#define vmail (&vifs)[1]
+#define vmpath (&vmail)[1]
+#else
+#define vmpath vifs
+#endif
+#define vpath (&vmpath)[1]
+#define vps1 (&vpath)[1]
+#define vps2 (&vps1)[1]
+#define vps4 (&vps2)[1]
+#define voptind (&vps4)[1]
+#if ENABLE_ASH_GETOPTS
+#define vrandom (&voptind)[1]
+#else
+#define vrandom (&vps4)[1]
+#endif
+#define defpath (defpathvar + 5)
/*
- * Generate a list of variables satisfying the given conditions.
+ * The following macros access the values of the above variables.
+ * They have to skip over the name. They return the null string
+ * for unset variables.
*/
-static char **
-listvars(int on, int off, char ***end)
-{
- struct var **vpp;
- struct var *vp;
- char **ep;
- int mask;
+#define ifsval() (vifs.text + 4)
+#define ifsset() ((vifs.flags & VUNSET) == 0)
+#define mailval() (vmail.text + 5)
+#define mpathval() (vmpath.text + 9)
+#define pathval() (vpath.text + 5)
+#define ps1val() (vps1.text + 4)
+#define ps2val() (vps2.text + 4)
+#define ps4val() (vps4.text + 4)
+#define optindval() (voptind.text + 7)
- STARTSTACKSTR(ep);
- vpp = vartab;
- mask = on | off;
- do {
- for (vp = *vpp; vp; vp = vp->next) {
- if ((vp->flags & mask) == on) {
- if (ep == stackstrend())
- ep = growstackstr();
- *ep++ = (char *) vp->text;
- }
- }
- } while (++vpp < vartab + VTABSIZE);
- if (ep == stackstrend())
- ep = growstackstr();
- if (end)
- *end = ep;
- *ep++ = NULL;
- return grabstackstr(ep);
-}
+#define mpathset() ((vmpath.flags & VUNSET) == 0)
+static struct var **hashvar(const char *);
-/* ============ Path search helper
- *
- * The variable path (passed by reference) should be set to the start
- * of the path before the first call; padvance will update
- * this value as it proceeds. Successive calls to padvance will return
- * the possible path expansions in sequence. If an option (indicated by
- * a percent sign) appears in the path entry then the global variable
- * pathopt will be set to point to it; otherwise pathopt will be set to
- * NULL.
+static int loopnest; /* current loop nesting level */
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
*/
-static const char *pathopt; /* set by padvance */
+struct redirtab {
+ struct redirtab *next;
+ int renamed[10];
+ int nullredirs;
+};
-static char *
-padvance(const char **path, const char *name)
-{
- const char *p;
- char *q;
- const char *start;
- size_t len;
+static struct redirtab *redirlist;
+static int nullredirs;
- if (*path == NULL)
- return NULL;
- start = *path;
- for (p = start; *p && *p != ':' && *p != '%'; p++);
- len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
- while (stackblocksize() < len)
- growstackblock();
- q = stackblock();
- if (p != start) {
- memcpy(q, start, p - start);
- q += p - start;
- *q++ = '/';
- }
- strcpy(q, name);
- pathopt = NULL;
- if (*p == '%') {
- pathopt = ++p;
- while (*p && *p != ':') p++;
- }
- if (*p == ':')
- *path = p + 1;
- else
- *path = NULL;
- return stalloc(len);
-}
+extern char **environ;
+static int preverrout_fd; /* save fd2 before print debug if xflag is set. */
-/* ============ Prompt */
+struct shparam {
+ int nparam; /* # of positional parameters (without $0) */
+ unsigned char malloc; /* if parameter list dynamically allocated */
+ char **p; /* parameter list */
+#if ENABLE_ASH_GETOPTS
+ int optind; /* next parameter to be processed by getopts */
+ int optoff; /* used by getopts */
+#endif
+};
-static int doprompt; /* if set, prompt the user */
-static int needprompt; /* true if interactive and at start of line */
+static struct shparam shellparam; /* $@ current positional parameters */
-#if ENABLE_FEATURE_EDITING
-static line_input_t *line_input_state;
-static const char *cmdedit_prompt;
-static void
-putprompt(const char *s)
-{
- if (ENABLE_ASH_EXPAND_PRMT) {
- free((char*)cmdedit_prompt);
- cmdedit_prompt = xstrdup(s);
- return;
- }
- cmdedit_prompt = s;
-}
-#else
+#define VTABSIZE 39
+
+static struct var *vartab[VTABSIZE];
+
+#if ENABLE_ASH_GETOPTS
static void
-putprompt(const char *s)
+getoptsreset(const char *value)
{
- out2str(s);
+ shellparam.optind = number(value);
+ shellparam.optoff = -1;
}
#endif
-#if ENABLE_ASH_EXPAND_PRMT
-/* expandstr() needs parsing machinery, so it is far away ahead... */
-static const char *expandstr(const char *ps);
-#else
-#define expandstr(s) s
-#endif
+#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
-static void
-setprompt(int whichprompt)
+/*
+ * Return of a legal variable name (a letter or underscore followed by zero or
+ * more letters, underscores, and digits).
+ */
+static char *
+endofname(const char *name)
{
- const char *prompt;
-#if ENABLE_ASH_EXPAND_PRMT
- struct stackmark smark;
-#endif
-
- needprompt = 0;
+ char *p;
- switch (whichprompt) {
- case 1:
- prompt = ps1val();
- break;
- case 2:
- prompt = ps2val();
- break;
- default: /* 0 */
- prompt = nullstr;
+ p = (char *) name;
+ if (!is_name(*p))
+ return p;
+ while (*++p) {
+ if (!is_in_name(*p))
+ break;
}
-#if ENABLE_ASH_EXPAND_PRMT
- setstackmark(&smark);
- stalloc(stackblocksize());
-#endif
- putprompt(expandstr(prompt));
-#if ENABLE_ASH_EXPAND_PRMT
- popstackmark(&smark);
-#endif
+ return p;
}
+/*
+ * Compares two strings up to the first = or '\0'. The first
+ * string must be terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+static int
+varcmp(const char *p, const char *q)
+{
+ int c, d;
-/* ============ The cd and pwd commands */
+ while ((c = *p) == (d = *q)) {
+ if (!c || c == '=')
+ goto out;
+ p++;
+ q++;
+ }
+ if (c == '=')
+ c = '\0';
+ if (d == '=')
+ d = '\0';
+ out:
+ return c - d;
+}
-#define CD_PHYSICAL 1
-#define CD_PRINT 2
+static int
+varequal(const char *a, const char *b)
+{
+ return !varcmp(a, b);
+}
-static int docd(const char *, int);
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+static struct var **
+hashvar(const char *p)
+{
+ unsigned hashval;
-static char *curdir = nullstr; /* current working directory */
-static char *physdir = nullstr; /* physical working directory */
+ hashval = ((unsigned char) *p) << 4;
+ while (*p && *p != '=')
+ hashval += (unsigned char) *p++;
+ return &vartab[hashval % VTABSIZE];
+}
static int
-cdopt(void)
+vpcmp(const void *a, const void *b)
{
- int flags = 0;
- int i, j;
-
- j = 'L';
- while ((i = nextopt("LP"))) {
- if (i != j) {
- flags ^= CD_PHYSICAL;
- j = i;
- }
- }
-
- return flags;
+ return varcmp(*(const char **)a, *(const char **)b);
}
/*
- * Update curdir (the name of the current directory) in response to a
- * cd command.
+ * This routine initializes the builtin variables.
*/
-static const char *
-updatepwd(const char *dir)
+static void
+initvar(void)
{
- char *new;
- char *p;
- char *cdcomppath;
- const char *lim;
+ struct var *vp;
+ struct var *end;
+ struct var **vpp;
- cdcomppath = ststrdup(dir);
- STARTSTACKSTR(new);
- if (*dir != '/') {
- if (curdir == nullstr)
- return 0;
- new = stack_putstr(curdir, new);
- }
- new = makestrspace(strlen(dir) + 2, new);
- lim = stackblock() + 1;
- if (*dir != '/') {
- if (new[-1] != '/')
- USTPUTC('/', new);
- if (new > lim && *lim == '/')
- lim++;
- } else {
- USTPUTC('/', new);
- cdcomppath++;
- if (dir[1] == '/' && dir[2] != '/') {
- USTPUTC('/', new);
- cdcomppath++;
- lim++;
- }
- }
- p = strtok(cdcomppath, "/");
- while (p) {
- switch (*p) {
- case '.':
- if (p[1] == '.' && p[2] == '\0') {
- while (new > lim) {
- STUNPUTC(new);
- if (new[-1] == '/')
- break;
- }
- break;
- } else if (p[1] == '\0')
- break;
- /* fall through */
- default:
- new = stack_putstr(p, new);
- USTPUTC('/', new);
+ /*
+ * PS1 depends on uid
+ */
+#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
+ vps1.text = "PS1=\\w \\$ ";
+#else
+ if (!geteuid())
+ vps1.text = "PS1=# ";
+#endif
+ vp = varinit;
+ end = vp + sizeof(varinit) / sizeof(varinit[0]);
+ do {
+ vpp = hashvar(vp->text);
+ vp->next = *vpp;
+ *vpp = vp;
+ } while (++vp < end);
+}
+
+static struct var **
+findvar(struct var **vpp, const char *name)
+{
+ for (; *vpp; vpp = &(*vpp)->next) {
+ if (varequal((*vpp)->text, name)) {
+ break;
}
- p = strtok(0, "/");
}
- if (new > lim)
- STUNPUTC(new);
- *new = 0;
- return stackblock();
+ return vpp;
}
/*
- * Find out what the current directory is. If we already know the current
- * directory, this routine returns immediately.
+ * Find the value of a variable. Returns NULL if not set.
*/
static char *
-getpwd(void)
+lookupvar(const char *name)
{
- char *dir = getcwd(0, 0);
- return dir ? dir : nullstr;
+ struct var *v;
+
+ v = *findvar(hashvar(name), name);
+ if (v) {
+#ifdef DYNAMIC_VAR
+ /*
+ * Dynamic variables are implemented roughly the same way they are
+ * in bash. Namely, they're "special" so long as they aren't unset.
+ * As soon as they're unset, they're no longer dynamic, and dynamic
+ * lookup will no longer happen at that point. -- PFM.
+ */
+ if ((v->flags & VDYNAMIC))
+ (*v->func)(NULL);
+#endif
+ if (!(v->flags & VUNSET))
+ return strchrnul(v->text, '=') + 1;
+ }
+ return NULL;
}
-static void
-setpwd(const char *val, int setold)
+/*
+ * Search the environment of a builtin command.
+ */
+static char *
+bltinlookup(const char *name)
{
- char *oldcur, *dir;
-
- oldcur = dir = curdir;
+ struct strlist *sp;
- if (setold) {
- setvar("OLDPWD", oldcur, VEXPORT);
- }
- INT_OFF;
- if (physdir != nullstr) {
- if (physdir != oldcur)
- free(physdir);
- physdir = nullstr;
- }
- if (oldcur == val || !val) {
- char *s = getpwd();
- physdir = s;
- if (!val)
- dir = s;
- } else
- dir = ckstrdup(val);
- if (oldcur != dir && oldcur != nullstr) {
- free(oldcur);
+ for (sp = cmdenviron; sp; sp = sp->next) {
+ if (varequal(sp->text, name))
+ return strchrnul(sp->text, '=') + 1;
}
- curdir = dir;
- INT_ON;
- setvar("PWD", dir, VEXPORT);
+ return lookupvar(name);
}
-static void hashcd(void);
-
/*
- * Actually do the chdir. We also call hashcd to let the routines in exec.c
- * know that the current directory has changed.
+ * Same as setvar except that the variable and value are passed in
+ * the first argument as name=value. Since the first argument will
+ * be actually stored in the table, it should not be a string that
+ * will go away.
+ * Called with interrupts off.
*/
-static int
-docd(const char *dest, int flags)
+static void
+setvareq(char *s, int flags)
{
- const char *dir = 0;
- int err;
+ struct var *vp, **vpp;
- TRACE(("docd(\"%s\", %d) called\n", dest, flags));
+ vpp = hashvar(s);
+ flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
+ vp = *findvar(vpp, s);
+ if (vp) {
+ if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
+ const char *n;
- INT_OFF;
- if (!(flags & CD_PHYSICAL)) {
- dir = updatepwd(dest);
- if (dir)
- dest = dir;
+ if (flags & VNOSAVE)
+ free(s);
+ n = vp->text;
+ ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
+ }
+
+ if (flags & VNOSET)
+ return;
+
+ if (vp->func && (flags & VNOFUNC) == 0)
+ (*vp->func)(strchrnul(s, '=') + 1);
+
+ if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+ free((char*)vp->text);
+
+ flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
+ } else {
+ if (flags & VNOSET)
+ return;
+ /* not found */
+ vp = ckmalloc(sizeof(*vp));
+ vp->next = *vpp;
+ vp->func = NULL;
+ *vpp = vp;
}
- err = chdir(dest);
- if (err)
- goto out;
- setpwd(dir, 1);
- hashcd();
- out:
- INT_ON;
- return err;
+ if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
+ s = ckstrdup(s);
+ vp->text = s;
+ vp->flags = flags;
}
-static int
-cdcmd(int argc, char **argv)
+/*
+ * Set the value of a variable. The flags argument is ored with the
+ * flags of the variable. If val is NULL, the variable is unset.
+ */
+static void
+setvar(const char *name, const char *val, int flags)
{
- const char *dest;
- const char *path;
- const char *p;
- char c;
- struct stat statb;
- int flags;
+ char *p, *q;
+ size_t namelen;
+ char *nameeq;
+ size_t vallen;
- flags = cdopt();
- dest = *argptr;
- if (!dest)
- dest = bltinlookup(homestr);
- else if (LONE_DASH(dest)) {
- dest = bltinlookup("OLDPWD");
- flags |= CD_PRINT;
- }
- if (!dest)
- dest = nullstr;
- if (*dest == '/')
- goto step7;
- if (*dest == '.') {
- c = dest[1];
- dotdot:
- switch (c) {
- case '\0':
- case '/':
- goto step6;
- case '.':
- c = dest[2];
- if (c != '.')
- goto dotdot;
- }
+ q = endofname(name);
+ p = strchrnul(q, '=');
+ namelen = p - name;
+ if (!namelen || p != q)
+ ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
+ vallen = 0;
+ if (val == NULL) {
+ flags |= VUNSET;
+ } else {
+ vallen = strlen(val);
}
- if (!*dest)
- dest = ".";
- path = bltinlookup("CDPATH");
- if (!path) {
- step6:
- step7:
- p = dest;
- goto docd;
+ INT_OFF;
+ nameeq = ckmalloc(namelen + vallen + 2);
+ p = memcpy(nameeq, name, namelen) + namelen;
+ if (val) {
+ *p++ = '=';
+ p = memcpy(p, val, vallen) + vallen;
}
- do {
- c = *path;
- p = padvance(&path, dest);
- if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
- if (c && c != ':')
- flags |= CD_PRINT;
- docd:
- if (!docd(p, flags))
- goto out;
- break;
- }
- } while (path);
- ash_msg_and_raise_error("can't cd to %s", dest);
- /* NOTREACHED */
- out:
- if (flags & CD_PRINT)
- out1fmt(snlfmt, curdir);
- return 0;
+ *p = '\0';
+ setvareq(nameeq, flags | VNOSAVE);
+ INT_ON;
}
+#if ENABLE_ASH_GETOPTS
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
static int
-pwdcmd(int argc, char **argv)
+setvarsafe(const char *name, const char *val, int flags)
{
- int flags;
- const char *dir = curdir;
+ int err;
+ volatile int saveint;
+ struct jmploc *volatile savehandler = exception_handler;
+ struct jmploc jmploc;
- flags = cdopt();
- if (flags) {
- if (physdir == nullstr)
- setpwd(dir, 0);
- dir = physdir;
+ SAVE_INT(saveint);
+ if (setjmp(jmploc.loc))
+ err = 1;
+ else {
+ exception_handler = &jmploc;
+ setvar(name, val, flags);
+ err = 0;
}
- out1fmt(snlfmt, dir);
- return 0;
+ exception_handler = savehandler;
+ RESTORE_INT(saveint);
+ return err;
}
-
-
-/* ============ Unsorted yet */
-
-
-/* expand.h */
-
-struct arglist {
- struct strlist *list;
- struct strlist **lastp;
-};
+#endif
/*
- * expandarg() flags
+ * Unset the specified variable.
*/
-#define EXP_FULL 0x1 /* perform word splitting & file globbing */
-#define EXP_TILDE 0x2 /* do normal tilde expansion */
-#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
-#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
-#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
-#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */
-#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
-#define EXP_WORD 0x80 /* expand word in parameter expansion */
-#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */
-
+static int
+unsetvar(const char *s)
+{
+ struct var **vpp;
+ struct var *vp;
+ int retval;
-union node;
-static void expandarg(union node *, struct arglist *, int);
-#define rmescapes(p) _rmescapes((p), 0)
-static char *_rmescapes(char *, int);
-static int casematch(union node *, char *);
+ vpp = findvar(hashvar(s), s);
+ vp = *vpp;
+ retval = 2;
+ if (vp) {
+ int flags = vp->flags;
-#if ENABLE_ASH_MATH_SUPPORT
-static void expari(int);
+ retval = 1;
+ if (flags & VREADONLY)
+ goto out;
+#ifdef DYNAMIC_VAR
+ vp->flags &= ~VDYNAMIC;
#endif
-
-/* eval.h */
-
-
-
-struct backcmd { /* result of evalbackcmd */
- int fd; /* file descriptor to read from */
- char *buf; /* buffer */
- int nleft; /* number of chars in buffer */
- struct job *jp; /* job structure for command */
-};
+ if (flags & VUNSET)
+ goto ok;
+ if ((flags & VSTRFIXED) == 0) {
+ INT_OFF;
+ if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+ free((char*)vp->text);
+ *vpp = vp->next;
+ free(vp);
+ INT_ON;
+ } else {
+ setvar(s, 0, 0);
+ vp->flags &= ~VEXPORT;
+ }
+ ok:
+ retval = 0;
+ }
+ out:
+ return retval;
+}
/*
- * This file was generated by the mknodes program.
+ * Process a linked list of variable assignments.
*/
+static void
+listsetvar(struct strlist *list_set_var, int flags)
+{
+ struct strlist *lp = list_set_var;
-#define NCMD 0
-#define NPIPE 1
-#define NREDIR 2
-#define NBACKGND 3
-#define NSUBSHELL 4
-#define NAND 5
-#define NOR 6
-#define NSEMI 7
-#define NIF 8
-#define NWHILE 9
-#define NUNTIL 10
-#define NFOR 11
-#define NCASE 12
-#define NCLIST 13
-#define NDEFUN 14
-#define NARG 15
-#define NTO 16
-#define NCLOBBER 17
-#define NFROM 18
-#define NFROMTO 19
-#define NAPPEND 20
-#define NTOFD 21
-#define NFROMFD 22
-#define NHERE 23
-#define NXHERE 24
-#define NNOT 25
-
+ if (!lp)
+ return;
+ INT_OFF;
+ do {
+ setvareq(lp->text, flags);
+ lp = lp->next;
+ } while (lp);
+ INT_ON;
+}
-struct ncmd {
- int type;
- union node *assign;
- union node *args;
- union node *redirect;
-};
+/*
+ * Generate a list of variables satisfying the given conditions.
+ */
+static char **
+listvars(int on, int off, char ***end)
+{
+ struct var **vpp;
+ struct var *vp;
+ char **ep;
+ int mask;
-struct npipe {
- int type;
- int backgnd;
- struct nodelist *cmdlist;
-};
+ STARTSTACKSTR(ep);
+ vpp = vartab;
+ mask = on | off;
+ do {
+ for (vp = *vpp; vp; vp = vp->next) {
+ if ((vp->flags & mask) == on) {
+ if (ep == stackstrend())
+ ep = growstackstr();
+ *ep++ = (char *) vp->text;
+ }
+ }
+ } while (++vpp < vartab + VTABSIZE);
+ if (ep == stackstrend())
+ ep = growstackstr();
+ if (end)
+ *end = ep;
+ *ep++ = NULL;
+ return grabstackstr(ep);
+}
-struct nredir {
- int type;
- union node *n;
- union node *redirect;
-};
-struct nbinary {
- int type;
- union node *ch1;
- union node *ch2;
-};
+/* ============ Path search helper
+ *
+ * The variable path (passed by reference) should be set to the start
+ * of the path before the first call; padvance will update
+ * this value as it proceeds. Successive calls to padvance will return
+ * the possible path expansions in sequence. If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ */
+static const char *pathopt; /* set by padvance */
-struct nif {
- int type;
- union node *test;
- union node *ifpart;
- union node *elsepart;
-};
+static char *
+padvance(const char **path, const char *name)
+{
+ const char *p;
+ char *q;
+ const char *start;
+ size_t len;
-struct nfor {
- int type;
- union node *args;
- union node *body;
- char *var;
-};
+ if (*path == NULL)
+ return NULL;
+ start = *path;
+ for (p = start; *p && *p != ':' && *p != '%'; p++);
+ len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
+ while (stackblocksize() < len)
+ growstackblock();
+ q = stackblock();
+ if (p != start) {
+ memcpy(q, start, p - start);
+ q += p - start;
+ *q++ = '/';
+ }
+ strcpy(q, name);
+ pathopt = NULL;
+ if (*p == '%') {
+ pathopt = ++p;
+ while (*p && *p != ':') p++;
+ }
+ if (*p == ':')
+ *path = p + 1;
+ else
+ *path = NULL;
+ return stalloc(len);
+}
-struct ncase {
- int type;
- union node *expr;
- union node *cases;
-};
-struct nclist {
- int type;
- union node *next;
- union node *pattern;
- union node *body;
-};
+/* ============ Prompt */
-struct narg {
- int type;
- union node *next;
- char *text;
- struct nodelist *backquote;
-};
+static int doprompt; /* if set, prompt the user */
+static int needprompt; /* true if interactive and at start of line */
-struct nfile {
- int type;
- union node *next;
- int fd;
- union node *fname;
- char *expfname;
-};
+#if ENABLE_FEATURE_EDITING
+static line_input_t *line_input_state;
+static const char *cmdedit_prompt;
+static void
+putprompt(const char *s)
+{
+ if (ENABLE_ASH_EXPAND_PRMT) {
+ free((char*)cmdedit_prompt);
+ cmdedit_prompt = xstrdup(s);
+ return;
+ }
+ cmdedit_prompt = s;
+}
+#else
+static void
+putprompt(const char *s)
+{
+ out2str(s);
+}
+#endif
-struct ndup {
- int type;
- union node *next;
- int fd;
- int dupfd;
- union node *vname;
-};
+#if ENABLE_ASH_EXPAND_PRMT
+/* expandstr() needs parsing machinery, so it is far away ahead... */
+static const char *expandstr(const char *ps);
+#else
+#define expandstr(s) s
+#endif
-struct nhere {
- int type;
- union node *next;
- int fd;
- union node *doc;
-};
+static void
+setprompt(int whichprompt)
+{
+ const char *prompt;
+#if ENABLE_ASH_EXPAND_PRMT
+ struct stackmark smark;
+#endif
-struct nnot {
- int type;
- union node *com;
-};
+ needprompt = 0;
-union node {
- int type;
- struct ncmd ncmd;
- struct npipe npipe;
- struct nredir nredir;
- struct nbinary nbinary;
- struct nif nif;
- struct nfor nfor;
- struct ncase ncase;
- struct nclist nclist;
- struct narg narg;
- struct nfile nfile;
- struct ndup ndup;
- struct nhere nhere;
- struct nnot nnot;
-};
+ switch (whichprompt) {
+ case 1:
+ prompt = ps1val();
+ break;
+ case 2:
+ prompt = ps2val();
+ break;
+ default: /* 0 */
+ prompt = nullstr;
+ }
+#if ENABLE_ASH_EXPAND_PRMT
+ setstackmark(&smark);
+ stalloc(stackblocksize());
+#endif
+ putprompt(expandstr(prompt));
+#if ENABLE_ASH_EXPAND_PRMT
+ popstackmark(&smark);
+#endif
+}
-struct nodelist {
- struct nodelist *next;
- union node *n;
-};
-struct funcnode {
- int count;
- union node n;
-};
+/* ============ The cd and pwd commands */
+#define CD_PHYSICAL 1
+#define CD_PRINT 2
-static void freefunc(struct funcnode *);
-/* parser.h */
-
-/* control characters in argument strings */
-#define CTL_FIRST '\201' /* first 'special' character */
-#define CTLESC '\201' /* escape next character */
-#define CTLVAR '\202' /* variable defn */
-#define CTLENDVAR '\203'
-#define CTLBACKQ '\204'
-#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */
-/* CTLBACKQ | CTLQUOTE == '\205' */
-#define CTLARI '\206' /* arithmetic expression */
-#define CTLENDARI '\207'
-#define CTLQUOTEMARK '\210'
-#define CTL_LAST '\210' /* last 'special' character */
+static int docd(const char *, int);
-/* variable substitution byte (follows CTLVAR) */
-#define VSTYPE 0x0f /* type of variable substitution */
-#define VSNUL 0x10 /* colon--treat the empty string as unset */
-#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */
+static char *curdir = nullstr; /* current working directory */
+static char *physdir = nullstr; /* physical working directory */
-/* values of VSTYPE field */
-#define VSNORMAL 0x1 /* normal variable: $var or ${var} */
-#define VSMINUS 0x2 /* ${var-text} */
-#define VSPLUS 0x3 /* ${var+text} */
-#define VSQUESTION 0x4 /* ${var?message} */
-#define VSASSIGN 0x5 /* ${var=text} */
-#define VSTRIMRIGHT 0x6 /* ${var%pattern} */
-#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */
-#define VSTRIMLEFT 0x8 /* ${var#pattern} */
-#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
-#define VSLENGTH 0xa /* ${#var} */
+static int
+cdopt(void)
+{
+ int flags = 0;
+ int i, j;
-/* values of checkkwd variable */
-#define CHKALIAS 0x1
-#define CHKKWD 0x2
-#define CHKNL 0x4
+ j = 'L';
+ while ((i = nextopt("LP"))) {
+ if (i != j) {
+ flags ^= CD_PHYSICAL;
+ j = i;
+ }
+ }
-#define IBUFSIZ (BUFSIZ + 1)
+ return flags;
+}
/*
- * NEOF is returned by parsecmd when it encounters an end of file. It
- * must be distinct from NULL, so we use the address of a variable that
- * happens to be handy.
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.
*/
-static int plinno = 1; /* input line number */
-
-/* number of characters left in input buffer */
-static int parsenleft; /* copy of parsefile->nleft */
-static int parselleft; /* copy of parsefile->lleft */
-
-/* next character in input buffer */
-static char *parsenextc; /* copy of parsefile->nextc */
+static const char *
+updatepwd(const char *dir)
+{
+ char *new;
+ char *p;
+ char *cdcomppath;
+ const char *lim;
-#define basebuf bb_common_bufsiz1 /* buffer for top level input file */
+ cdcomppath = ststrdup(dir);
+ STARTSTACKSTR(new);
+ if (*dir != '/') {
+ if (curdir == nullstr)
+ return 0;
+ new = stack_putstr(curdir, new);
+ }
+ new = makestrspace(strlen(dir) + 2, new);
+ lim = stackblock() + 1;
+ if (*dir != '/') {
+ if (new[-1] != '/')
+ USTPUTC('/', new);
+ if (new > lim && *lim == '/')
+ lim++;
+ } else {
+ USTPUTC('/', new);
+ cdcomppath++;
+ if (dir[1] == '/' && dir[2] != '/') {
+ USTPUTC('/', new);
+ cdcomppath++;
+ lim++;
+ }
+ }
+ p = strtok(cdcomppath, "/");
+ while (p) {
+ switch (*p) {
+ case '.':
+ if (p[1] == '.' && p[2] == '\0') {
+ while (new > lim) {
+ STUNPUTC(new);
+ if (new[-1] == '/')
+ break;
+ }
+ break;
+ } else if (p[1] == '\0')
+ break;
+ /* fall through */
+ default:
+ new = stack_putstr(p, new);
+ USTPUTC('/', new);
+ }
+ p = strtok(0, "/");
+ }
+ if (new > lim)
+ STUNPUTC(new);
+ *new = 0;
+ return stackblock();
+}
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+static char *
+getpwd(void)
+{
+ char *dir = getcwd(0, 0);
+ return dir ? dir : nullstr;
+}
-static int tokpushback; /* last token pushed back */
-#define NEOF ((union node *)&tokpushback)
-static int parsebackquote; /* nonzero if we are inside backquotes */
-static int lasttoken; /* last token read */
-static char *wordtext; /* text of last word returned by readtoken */
-static int checkkwd;
-static struct nodelist *backquotelist;
-static union node *redirnode;
-static struct heredoc *heredoc;
-static int quoteflag; /* set if (part of) last token was quoted */
+static void
+setpwd(const char *val, int setold)
+{
+ char *oldcur, *dir;
-static void fixredir(union node *, const char *, int);
-static char *endofname(const char *);
+ oldcur = dir = curdir;
-/* shell.h */
+ if (setold) {
+ setvar("OLDPWD", oldcur, VEXPORT);
+ }
+ INT_OFF;
+ if (physdir != nullstr) {
+ if (physdir != oldcur)
+ free(physdir);
+ physdir = nullstr;
+ }
+ if (oldcur == val || !val) {
+ char *s = getpwd();
+ physdir = s;
+ if (!val)
+ dir = s;
+ } else
+ dir = ckstrdup(val);
+ if (oldcur != dir && oldcur != nullstr) {
+ free(oldcur);
+ }
+ curdir = dir;
+ INT_ON;
+ setvar("PWD", dir, VEXPORT);
+}
-static const char spcstr[] = " ";
-static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' };
+static void hashcd(void);
-#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
-#define __builtin_expect(x, expected_value) (x)
-#endif
+/*
+ * Actually do the chdir. We also call hashcd to let the routines in exec.c
+ * know that the current directory has changed.
+ */
+static int
+docd(const char *dest, int flags)
+{
+ const char *dir = 0;
+ int err;
-#define xlikely(x) __builtin_expect((x),1)
+ TRACE(("docd(\"%s\", %d) called\n", dest, flags));
+ INT_OFF;
+ if (!(flags & CD_PHYSICAL)) {
+ dir = updatepwd(dest);
+ if (dir)
+ dest = dir;
+ }
+ err = chdir(dest);
+ if (err)
+ goto out;
+ setpwd(dir, 1);
+ hashcd();
+ out:
+ INT_ON;
+ return err;
+}
-#define TEOF 0
-#define TNL 1
-#define TREDIR 2
-#define TWORD 3
-#define TSEMI 4
-#define TBACKGND 5
-#define TAND 6
-#define TOR 7
-#define TPIPE 8
-#define TLP 9
-#define TRP 10
-#define TENDCASE 11
-#define TENDBQUOTE 12
-#define TNOT 13
-#define TCASE 14
-#define TDO 15
-#define TDONE 16
-#define TELIF 17
-#define TELSE 18
-#define TESAC 19
-#define TFI 20
-#define TFOR 21
-#define TIF 22
-#define TIN 23
-#define TTHEN 24
-#define TUNTIL 25
-#define TWHILE 26
-#define TBEGIN 27
-#define TEND 28
+static int
+cdcmd(int argc, char **argv)
+{
+ const char *dest;
+ const char *path;
+ const char *p;
+ char c;
+ struct stat statb;
+ int flags;
-/* first char is indicating which tokens mark the end of a list */
-static const char *const tokname_array[] = {
- "\1end of file",
- "\0newline",
- "\0redirection",
- "\0word",
- "\0;",
- "\0&",
- "\0&&",
- "\0||",
- "\0|",
- "\0(",
+ flags = cdopt();
+ dest = *argptr;
+ if (!dest)
+ dest = bltinlookup(homestr);
+ else if (LONE_DASH(dest)) {
+ dest = bltinlookup("OLDPWD");
+ flags |= CD_PRINT;
+ }
+ if (!dest)
+ dest = nullstr;
+ if (*dest == '/')
+ goto step7;
+ if (*dest == '.') {
+ c = dest[1];
+ dotdot:
+ switch (c) {
+ case '\0':
+ case '/':
+ goto step6;
+ case '.':
+ c = dest[2];
+ if (c != '.')
+ goto dotdot;
+ }
+ }
+ if (!*dest)
+ dest = ".";
+ path = bltinlookup("CDPATH");
+ if (!path) {
+ step6:
+ step7:
+ p = dest;
+ goto docd;
+ }
+ do {
+ c = *path;
+ p = padvance(&path, dest);
+ if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+ if (c && c != ':')
+ flags |= CD_PRINT;
+ docd:
+ if (!docd(p, flags))
+ goto out;
+ break;
+ }
+ } while (path);
+ ash_msg_and_raise_error("can't cd to %s", dest);
+ /* NOTREACHED */
+ out:
+ if (flags & CD_PRINT)
+ out1fmt(snlfmt, curdir);
+ return 0;
+}
+
+static int
+pwdcmd(int argc, char **argv)
+{
+ int flags;
+ const char *dir = curdir;
+
+ flags = cdopt();
+ if (flags) {
+ if (physdir == nullstr)
+ setpwd(dir, 0);
+ dir = physdir;
+ }
+ out1fmt(snlfmt, dir);
+ return 0;
+}
+
+
+/* ============ Unsorted yet */
+
+
+/* expand.h */
+
+struct arglist {
+ struct strlist *list;
+ struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_FULL 0x1 /* perform word splitting & file globbing */
+#define EXP_TILDE 0x2 /* do normal tilde expansion */
+#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
+#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
+#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
+#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */
+#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
+#define EXP_WORD 0x80 /* expand word in parameter expansion */
+#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */
+
+
+static void expandarg(union node *, struct arglist *, int);
+#define rmescapes(p) _rmescapes((p), 0)
+static char *_rmescapes(char *, int);
+static int casematch(union node *, char *);
+
+#if ENABLE_ASH_MATH_SUPPORT
+static void expari(int);
+#endif
+
+/* eval.h */
+
+
+
+struct backcmd { /* result of evalbackcmd */
+ int fd; /* file descriptor to read from */
+ char *buf; /* buffer */
+ int nleft; /* number of chars in buffer */
+ struct job *jp; /* job structure for command */
+};
+
+
+static void freefunc(struct funcnode *);
+/* parser.h */
+
+/* control characters in argument strings */
+#define CTL_FIRST '\201' /* first 'special' character */
+#define CTLESC '\201' /* escape next character */
+#define CTLVAR '\202' /* variable defn */
+#define CTLENDVAR '\203'
+#define CTLBACKQ '\204'
+#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */
+/* CTLBACKQ | CTLQUOTE == '\205' */
+#define CTLARI '\206' /* arithmetic expression */
+#define CTLENDARI '\207'
+#define CTLQUOTEMARK '\210'
+#define CTL_LAST '\210' /* last 'special' character */
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE 0x0f /* type of variable substitution */
+#define VSNUL 0x10 /* colon--treat the empty string as unset */
+#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */
+
+/* values of VSTYPE field */
+#define VSNORMAL 0x1 /* normal variable: $var or ${var} */
+#define VSMINUS 0x2 /* ${var-text} */
+#define VSPLUS 0x3 /* ${var+text} */
+#define VSQUESTION 0x4 /* ${var?message} */
+#define VSASSIGN 0x5 /* ${var=text} */
+#define VSTRIMRIGHT 0x6 /* ${var%pattern} */
+#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */
+#define VSTRIMLEFT 0x8 /* ${var#pattern} */
+#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
+#define VSLENGTH 0xa /* ${#var} */
+
+/* values of checkkwd variable */
+#define CHKALIAS 0x1
+#define CHKKWD 0x2
+#define CHKNL 0x4
+
+#define IBUFSIZ (BUFSIZ + 1)
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file. It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+static int plinno = 1; /* input line number */
+
+/* number of characters left in input buffer */
+static int parsenleft; /* copy of parsefile->nleft */
+static int parselleft; /* copy of parsefile->lleft */
+
+/* next character in input buffer */
+static char *parsenextc; /* copy of parsefile->nextc */
+
+#define basebuf bb_common_bufsiz1 /* buffer for top level input file */
+
+
+static int tokpushback; /* last token pushed back */
+#define NEOF ((union node *)&tokpushback)
+static int parsebackquote; /* nonzero if we are inside backquotes */
+static int lasttoken; /* last token read */
+static char *wordtext; /* text of last word returned by readtoken */
+static int checkkwd;
+static struct nodelist *backquotelist;
+static union node *redirnode;
+static struct heredoc *heredoc;
+static int quoteflag; /* set if (part of) last token was quoted */
+
+static void fixredir(union node *, const char *, int);
+static char *endofname(const char *);
+
+/* shell.h */
+
+static const char spcstr[] = " ";
+static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' };
+
+#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+#define __builtin_expect(x, expected_value) (x)
+#endif
+
+#define xlikely(x) __builtin_expect((x),1)
+
+
+#define TEOF 0
+#define TNL 1
+#define TREDIR 2
+#define TWORD 3
+#define TSEMI 4
+#define TBACKGND 5
+#define TAND 6
+#define TOR 7
+#define TPIPE 8
+#define TLP 9
+#define TRP 10
+#define TENDCASE 11
+#define TENDBQUOTE 12
+#define TNOT 13
+#define TCASE 14
+#define TDO 15
+#define TDONE 16
+#define TELIF 17
+#define TELSE 18
+#define TESAC 19
+#define TFI 20
+#define TFOR 21
+#define TIF 22
+#define TIN 23
+#define TTHEN 24
+#define TUNTIL 25
+#define TWHILE 26
+#define TBEGIN 27
+#define TEND 28
+
+/* first char is indicating which tokens mark the end of a list */
+static const char *const tokname_array[] = {
+ "\1end of file",
+ "\0newline",
+ "\0redirection",
+ "\0word",
+ "\0;",
+ "\0&",
+ "\0&&",
+ "\0||",
+ "\0|",
+ "\0(",
"\1)",
"\1;;",
"\1`",
*/
#if JOBS
-static int bgcmd(int, char **);
+static int fg_bgcmd(int, char **);
#endif
static int breakcmd(int, char **);
static int cdcmd(int, char **);
static int exitcmd(int, char **);
static int exportcmd(int, char **);
static int falsecmd(int, char **);
-#if JOBS
-static int fgcmd(int, char **);
-#endif
#if ENABLE_ASH_GETOPTS
static int getoptscmd(int, char **);
#endif
{ BUILTIN_REG_ASSG "alias", aliascmd },
#endif
#if JOBS
- { BUILTIN_REGULAR "bg", bgcmd },
+ { BUILTIN_REGULAR "bg", fg_bgcmd },
#endif
{ BUILTIN_SPEC_REG "break", breakcmd },
{ BUILTIN_REGULAR "cd", cdcmd },
{ BUILTIN_SPEC_REG_ASSG "export", exportcmd },
{ BUILTIN_REGULAR "false", falsecmd },
#if JOBS
- { BUILTIN_REGULAR "fg", fgcmd },
+ { BUILTIN_REGULAR "fg", fg_bgcmd },
#endif
#if ENABLE_ASH_GETOPTS
{ BUILTIN_REGULAR "getopts", getoptscmd },
static void showjobs(FILE *, int);
#endif
-/* main.h */
+/* main.h */
static void readcmdfile(char *);
-/* mystring.h */
-
-
-#define DOLATSTRLEN 4
-
-static char *prefix(const char *, const char *);
-static int number(const char *);
-static int is_number(const char *);
-static char *single_quote(const char *);
-
-#define equal(s1, s2) (strcmp(s1, s2) == 0)
-#define scopy(s1, s2) ((void)strcpy(s2, s1))
-
/* options.h */
static char *minusc; /* argument to -c option */
static int copyfd(int, int);
static int redirectsafe(union node *, int);
-/* show.h */
-
-
-#if DEBUG
-static void showtree(union node *);
-static void trace(const char *, ...);
-static void tracev(const char *, va_list);
-static void trargs(char **);
-static void trputc(int);
-static void trputs(const char *);
-static void opentrace(void);
-#endif
-
/* trap.h */
-
static void clear_traps(void);
static void setsignal(int);
static void ignoresig(int);
#if ENABLE_ASH_ALIAS
+struct alias {
+ struct alias *next;
+ char *name;
+ char *val;
+ int flag;
+};
+
static struct alias *atab[ATABSIZE];
-static void setalias(const char *, const char *);
-static struct alias *freealias(struct alias *);
-static struct alias **__lookupalias(const char *);
+static struct alias **
+__lookupalias(const char *name) {
+ unsigned int hashval;
+ struct alias **app;
+ const char *p;
+ unsigned int ch;
+
+ p = name;
+
+ ch = (unsigned char)*p;
+ hashval = ch << 4;
+ while (ch) {
+ hashval += ch;
+ ch = (unsigned char)*++p;
+ }
+ app = &atab[hashval % ATABSIZE];
+
+ for (; *app; app = &(*app)->next) {
+ if (strcmp(name, (*app)->name) == 0) {
+ break;
+ }
+ }
+
+ return app;
+}
+
+static struct alias *
+lookupalias(const char *name, int check)
+{
+ struct alias *ap = *__lookupalias(name);
+
+ if (check && ap && (ap->flag & ALIASINUSE))
+ return NULL;
+ return ap;
+}
+
+static struct alias *
+freealias(struct alias *ap)
+{
+ struct alias *next;
+
+ if (ap->flag & ALIASINUSE) {
+ ap->flag |= ALIASDEAD;
+ return ap;
+ }
+
+ next = ap->next;
+ free(ap->name);
+ free(ap->val);
+ free(ap);
+ return next;
+}
static void
setalias(const char *name, const char *val)
INT_ON;
}
-static struct alias *
-lookupalias(const char *name, int check)
-{
- struct alias *ap = *__lookupalias(name);
-
- if (check && ap && (ap->flag & ALIASINUSE))
- return NULL;
- return ap;
-}
-
/*
* TODO - sort output
*/
return i;
}
-static struct alias *
-freealias(struct alias *ap)
-{
- struct alias *next;
-
- if (ap->flag & ALIASINUSE) {
- ap->flag |= ALIASDEAD;
- return ap;
- }
-
- next = ap->next;
- free(ap->name);
- free(ap->val);
- free(ap);
- return next;
-}
-
static void
printalias(const struct alias *ap)
{
out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
}
-
-static struct alias **
-__lookupalias(const char *name) {
- unsigned int hashval;
- struct alias **app;
- const char *p;
- unsigned int ch;
-
- p = name;
-
- ch = (unsigned char)*p;
- hashval = ch << 4;
- while (ch) {
- hashval += ch;
- ch = (unsigned char)*++p;
- }
- app = &atab[hashval % ATABSIZE];
-
- for (; *app; app = &(*app)->next) {
- if (equal(name, (*app)->name)) {
- break;
- }
- }
-
- return app;
-}
#endif /* ASH_ALIAS */
/* eval.c */
flags |= EV_EXIT;
if (backgnd)
flags &=~ EV_TESTED;
-nofork:
+ nofork:
redirect(n->nredir.redirect, 0);
evaltreenr(n->nredir.n, flags);
/* never returns */
hashval &= 0x7FFF;
pp = &cmdtable[hashval % CMDTABLESIZE];
for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
- if (equal(cmdp->cmdname, name))
+ if (strcmp(cmdp->cmdname, name) == 0)
break;
pp = &cmdp->next;
}
/* "$@" syntax adherence hack */
if (
!inquotes &&
- !memcmp(p, dolatstr, DOLATSTRLEN) &&
+ !memcmp(p, dolatstr, 4) &&
(p[4] == CTLQUOTEMARK || (
p[4] == CTLENDVAR &&
p[5] == CTLQUOTEMARK
continue;
if (pmatch(start, dp->d_name)) {
if (atend) {
- scopy(dp->d_name, enddir);
+ strcpy(enddir, dp->d_name);
addfname(expdir);
} else {
for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
#if JOBS
static int
-fgcmd(int argc, char **argv)
+fg_bgcmd(int argc, char **argv)
{
struct job *jp;
FILE *out;
return retval;
}
-static int bgcmd(int, char **) __attribute__((__alias__("fgcmd")));
-
-
static int
restartjob(struct job *jp, int mode)
{
psend = ps + jp->nprocs;
if (jp->state == JOBRUNNING) {
- scopy("Running", s + col);
- col += strlen("Running");
+ strcpy(s + col, "Running");
+ col += sizeof("Running") - 1;
} else {
int status = psend[-1].status;
#if JOBS
FILE *out;
mode = 0;
- while ((m = nextopt("lp")))
+ while ((m = nextopt("lp"))) {
if (m == 'l')
mode = SHOW_PID;
else
mode = SHOW_PGID;
+ }
out = stdout;
argv = argptr;
- if (*argv)
+ if (*argv) {
do
showjob(out, getjob(*argv,0), mode);
while (*++argv);
- else
+ } else
showjobs(out, mode);
return 0;
currentjob:
err_msg = "No current job";
goto check;
- } else if (c == '-') {
+ }
+ if (c == '-') {
if (jp)
jp = jp->prev_job;
err_msg = "No previous job";
* Return a new job structure.
* Called with interrupts off.
*/
-
static struct job *
makejob(union node *node, int nprocs)
{
static void forkparent(struct job *jp, union node *n, int mode, pid_t pid)
{
- TRACE(("In parent shell: child = %d\n", pid));
+ TRACE(("In parent shell: child = %d\n", pid));
if (!jp) {
while (jobless && dowait(DOWAIT_NORMAL, 0) > 0);
jobless++;
else
pgrp = jp->ps[0].pid;
/* This can fail because we are doing it in the child also */
- (void)setpgid(pid, pgrp);
+ setpgid(pid, pgrp);
}
#endif
if (mode == FORK_BG) {
* (as opposed to running a builtin command or just typing return),
* and the jobs command may give out of date information.
*/
-static int waitproc(int block, int *status)
+static int
+waitproc(int block, int *status)
{
int flags = 0;
s[0] = n->ndup.dupfd + '0';
p = s;
goto dotail2;
- } else {
- n = n->nfile.fname;
- goto donode;
}
+ n = n->nfile.fname;
+ goto donode;
}
}
"", "}", "-", "+", "?", "=",
"%", "%%", "#", "##"
};
+
nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
p = s;
while ((c = *p++) != 0) {
str = "${#";
else
str = "${";
- if (!(subtype & VSQUOTE) != !(quoted & 1)) {
- quoted ^= 1;
- c = '"';
- } else
+ if (!(subtype & VSQUOTE) == !(quoted & 1))
goto dostr;
+ quoted ^= 1;
+ c = '"';
break;
case CTLENDVAR:
str = "\"}" + !(quoted & 1);
if (name) {
for (i = 0; i < NOPTS; i++) {
- if (equal(name, optnames(i))) {
+ if (strcmp(name, optnames(i)) == 0) {
optlist[i] = val;
return;
}
#if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value)
+static void
+change_lc_all(const char *value)
{
if (value && *value != '\0')
setlocale(LC_ALL, value);
}
-static void change_lc_ctype(const char *value)
+static void
+change_lc_ctype(const char *value)
{
if (value && *value != '\0')
setlocale(LC_CTYPE, value);
#if ENABLE_ASH_RANDOM_SUPPORT
/* Roughly copied from bash.. */
-static void change_random(const char *value)
+static void
+change_random(const char *value)
{
if (value == NULL) {
/* "get", generate */
break;
default:
#if DEBUG
- abort();
-#endif
- /* Fall through to eliminate warning. */
- case NTOFD:
- case NFROMFD:
- f = -1;
- break;
- case NHERE:
- case NXHERE:
- f = openhere(redir);
- break;
- }
-
- return f;
- ecreate:
- ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent"));
- eopen:
- ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
-}
-
-static void
-dupredirect(union node *redir, int f)
-{
- int fd = redir->nfile.fd;
-
- if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
- if (redir->ndup.dupfd >= 0) { /* if not ">&-" */
- copyfd(redir->ndup.dupfd, fd);
- }
- return;
- }
-
- if (f != fd) {
- copyfd(f, fd);
- close(f);
- }
-}
-
-
-/*
- * Process a list of redirection commands. If the REDIR_PUSH flag is set,
- * old file descriptors are stashed away so that the redirection can be
- * undone by calling popredir. If the REDIR_BACKQ flag is set, then the
- * standard output, and the standard error if it becomes a duplicate of
- * stdout, is saved in memory.
- */
-static void
-redirect(union node *redir, int flags)
-{
- union node *n;
- struct redirtab *sv;
- int i;
- int fd;
- int newfd;
- int *p;
- nullredirs++;
- if (!redir) {
- return;
- }
- sv = NULL;
- INT_OFF;
- if (flags & REDIR_PUSH) {
- struct redirtab *q;
- q = ckmalloc(sizeof(struct redirtab));
- q->next = redirlist;
- redirlist = q;
- q->nullredirs = nullredirs - 1;
- for (i = 0; i < 10; i++)
- q->renamed[i] = EMPTY;
- nullredirs = 0;
- sv = q;
- }
- n = redir;
- do {
- fd = n->nfile.fd;
- if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
- && n->ndup.dupfd == fd)
- continue; /* redirect from/to same file descriptor */
-
- newfd = openredirect(n);
- if (fd == newfd)
- continue;
- if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
- i = fcntl(fd, F_DUPFD, 10);
-
- if (i == -1) {
- i = errno;
- if (i != EBADF) {
- close(newfd);
- errno = i;
- ash_msg_and_raise_error("%d: %m", fd);
- /* NOTREACHED */
- }
- } else {
- *p = i;
- close(fd);
- }
- } else {
- close(fd);
- }
- dupredirect(n, newfd);
- } while ((n = n->nfile.next));
- INT_ON;
- if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0)
- preverrout_fd = sv->renamed[2];
-}
-
-
-/*
- * Undo the effects of the last redirection.
- */
-static void
-popredir(int drop)
-{
- struct redirtab *rp;
- int i;
-
- if (--nullredirs >= 0)
- return;
- INT_OFF;
- rp = redirlist;
- for (i = 0; i < 10; i++) {
- if (rp->renamed[i] != EMPTY) {
- if (!drop) {
- close(i);
- copyfd(rp->renamed[i], i);
- }
- close(rp->renamed[i]);
- }
- }
- redirlist = rp->next;
- nullredirs = rp->nullredirs;
- free(rp);
- INT_ON;
-}
-
-/*
- * Undo all redirections. Called on error or interrupt.
- */
-
-/*
- * Discard all saved file descriptors.
- */
-static void
-clearredir(int drop)
-{
- for (;;) {
- nullredirs = 0;
- if (!redirlist)
- break;
- popredir(drop);
- }
-}
-
-
-/*
- * Copy a file descriptor to be >= to. Returns -1
- * if the source file descriptor is closed, EMPTY if there are no unused
- * file descriptors left.
- */
-static int
-copyfd(int from, int to)
-{
- int newfd;
-
- newfd = fcntl(from, F_DUPFD, to);
- if (newfd < 0) {
- if (errno == EMFILE)
- return EMPTY;
- ash_msg_and_raise_error("%d: %m", from);
- }
- return newfd;
-}
-
-static int
-redirectsafe(union node *redir, int flags)
-{
- int err;
- volatile int saveint;
- struct jmploc *volatile savehandler = exception_handler;
- struct jmploc jmploc;
-
- SAVE_INT(saveint);
- err = setjmp(jmploc.loc) * 2;
- if (!err) {
- exception_handler = &jmploc;
- redirect(redir, flags);
- }
- exception_handler = savehandler;
- if (err && exception != EXERROR)
- longjmp(exception_handler->loc, 1);
- RESTORE_INT(saveint);
- return err;
-}
-
-/* show.c */
-
-#if DEBUG
-static void shtree(union node *, int, char *, FILE*);
-static void shcmd(union node *, FILE *);
-static void sharg(union node *, FILE *);
-static void indent(int, char *, FILE *);
-static void trstring(char *);
-
-static void
-showtree(union node *n)
-{
- trputs("showtree called\n");
- shtree(n, 1, NULL, stdout);
-}
-
-static void
-shtree(union node *n, int ind, char *pfx, FILE *fp)
-{
- struct nodelist *lp;
- const char *s;
-
- if (n == NULL)
- return;
-
- indent(ind, pfx, fp);
- switch (n->type) {
- case NSEMI:
- s = "; ";
- goto binop;
- case NAND:
- s = " && ";
- goto binop;
- case NOR:
- s = " || ";
- binop:
- shtree(n->nbinary.ch1, ind, NULL, fp);
- /* if (ind < 0) */
- fputs(s, fp);
- shtree(n->nbinary.ch2, ind, NULL, fp);
- break;
- case NCMD:
- shcmd(n, fp);
- if (ind >= 0)
- putc('\n', fp);
- break;
- case NPIPE:
- for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
- shcmd(lp->n, fp);
- if (lp->next)
- fputs(" | ", fp);
- }
- if (n->npipe.backgnd)
- fputs(" &", fp);
- if (ind >= 0)
- putc('\n', fp);
- break;
- default:
- fprintf(fp, "<node type %d>", n->type);
- if (ind >= 0)
- putc('\n', fp);
- break;
- }
-}
-
-static void
-shcmd(union node *cmd, FILE *fp)
-{
- union node *np;
- int first;
- const char *s;
- int dftfd;
-
- first = 1;
- for (np = cmd->ncmd.args; np; np = np->narg.next) {
- if (! first)
- putchar(' ');
- sharg(np, fp);
- first = 0;
- }
- for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
- if (! first)
- putchar(' ');
- switch (np->nfile.type) {
- case NTO: s = ">"; dftfd = 1; break;
- case NCLOBBER: s = ">|"; dftfd = 1; break;
- case NAPPEND: s = ">>"; dftfd = 1; break;
- case NTOFD: s = ">&"; dftfd = 1; break;
- case NFROM: s = "<"; dftfd = 0; break;
- case NFROMFD: s = "<&"; dftfd = 0; break;
- case NFROMTO: s = "<>"; dftfd = 0; break;
- default: s = "*error*"; dftfd = 0; break;
- }
- if (np->nfile.fd != dftfd)
- fprintf(fp, "%d", np->nfile.fd);
- fputs(s, fp);
- if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
- fprintf(fp, "%d", np->ndup.dupfd);
- } else {
- sharg(np->nfile.fname, fp);
- }
- first = 0;
- }
-}
-
-static void
-sharg(union node *arg, FILE *fp)
-{
- char *p;
- struct nodelist *bqlist;
- int subtype;
-
- if (arg->type != NARG) {
- out1fmt("<node type %d>\n", arg->type);
- abort();
- }
- bqlist = arg->narg.backquote;
- for (p = arg->narg.text; *p; p++) {
- switch (*p) {
- case CTLESC:
- putc(*++p, fp);
- break;
- case CTLVAR:
- putc('$', fp);
- putc('{', fp);
- subtype = *++p;
- if (subtype == VSLENGTH)
- putc('#', fp);
-
- while (*p != '=')
- putc(*p++, fp);
-
- if (subtype & VSNUL)
- putc(':', fp);
-
- switch (subtype & VSTYPE) {
- case VSNORMAL:
- putc('}', fp);
- break;
- case VSMINUS:
- putc('-', fp);
- break;
- case VSPLUS:
- putc('+', fp);
- break;
- case VSQUESTION:
- putc('?', fp);
- break;
- case VSASSIGN:
- putc('=', fp);
- break;
- case VSTRIMLEFT:
- putc('#', fp);
- break;
- case VSTRIMLEFTMAX:
- putc('#', fp);
- putc('#', fp);
- break;
- case VSTRIMRIGHT:
- putc('%', fp);
- break;
- case VSTRIMRIGHTMAX:
- putc('%', fp);
- putc('%', fp);
- break;
- case VSLENGTH:
- break;
- default:
- out1fmt("<subtype %d>", subtype);
- }
- break;
- case CTLENDVAR:
- putc('}', fp);
- break;
- case CTLBACKQ:
- case CTLBACKQ|CTLQUOTE:
- putc('$', fp);
- putc('(', fp);
- shtree(bqlist->n, -1, NULL, fp);
- putc(')', fp);
- break;
- default:
- putc(*p, fp);
- break;
- }
+ abort();
+#endif
+ /* Fall through to eliminate warning. */
+ case NTOFD:
+ case NFROMFD:
+ f = -1;
+ break;
+ case NHERE:
+ case NXHERE:
+ f = openhere(redir);
+ break;
}
-}
+ return f;
+ ecreate:
+ ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent"));
+ eopen:
+ ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
+}
static void
-indent(int amount, char *pfx, FILE *fp)
+dupredirect(union node *redir, int f)
{
- int i;
+ int fd = redir->nfile.fd;
- for (i = 0; i < amount; i++) {
- if (pfx && i == amount - 1)
- fputs(pfx, fp);
- putc('\t', fp);
+ if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+ if (redir->ndup.dupfd >= 0) { /* if not ">&-" */
+ copyfd(redir->ndup.dupfd, fd);
+ }
+ return;
+ }
+
+ if (f != fd) {
+ copyfd(f, fd);
+ close(f);
}
}
/*
- * Debugging stuff.
+ * Process a list of redirection commands. If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir. If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
*/
-
-
-static FILE *tracefile;
-
-
-static void
-trputc(int c)
-{
- if (debug != 1)
- return;
- putc(c, tracefile);
-}
-
-static void
-trace(const char *fmt, ...)
-{
- va_list va;
-
- if (debug != 1)
- return;
- va_start(va, fmt);
- (void) vfprintf(tracefile, fmt, va);
- va_end(va);
-}
-
static void
-tracev(const char *fmt, va_list va)
+redirect(union node *redir, int flags)
{
- if (debug != 1)
+ union node *n;
+ struct redirtab *sv;
+ int i;
+ int fd;
+ int newfd;
+ int *p;
+ nullredirs++;
+ if (!redir) {
return;
- (void) vfprintf(tracefile, fmt, va);
-}
+ }
+ sv = NULL;
+ INT_OFF;
+ if (flags & REDIR_PUSH) {
+ struct redirtab *q;
+ q = ckmalloc(sizeof(struct redirtab));
+ q->next = redirlist;
+ redirlist = q;
+ q->nullredirs = nullredirs - 1;
+ for (i = 0; i < 10; i++)
+ q->renamed[i] = EMPTY;
+ nullredirs = 0;
+ sv = q;
+ }
+ n = redir;
+ do {
+ fd = n->nfile.fd;
+ if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
+ && n->ndup.dupfd == fd)
+ continue; /* redirect from/to same file descriptor */
+ newfd = openredirect(n);
+ if (fd == newfd)
+ continue;
+ if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
+ i = fcntl(fd, F_DUPFD, 10);
-static void
-trputs(const char *s)
-{
- if (debug != 1)
- return;
- fputs(s, tracefile);
+ if (i == -1) {
+ i = errno;
+ if (i != EBADF) {
+ close(newfd);
+ errno = i;
+ ash_msg_and_raise_error("%d: %m", fd);
+ /* NOTREACHED */
+ }
+ } else {
+ *p = i;
+ close(fd);
+ }
+ } else {
+ close(fd);
+ }
+ dupredirect(n, newfd);
+ } while ((n = n->nfile.next));
+ INT_ON;
+ if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0)
+ preverrout_fd = sv->renamed[2];
}
+/*
+ * Undo the effects of the last redirection.
+ */
static void
-trstring(char *s)
+popredir(int drop)
{
- char *p;
- char c;
+ struct redirtab *rp;
+ int i;
- if (debug != 1)
+ if (--nullredirs >= 0)
return;
- putc('"', tracefile);
- for (p = s; *p; p++) {
- switch (*p) {
- case '\n': c = 'n'; goto backslash;
- case '\t': c = 't'; goto backslash;
- case '\r': c = 'r'; goto backslash;
- case '"': c = '"'; goto backslash;
- case '\\': c = '\\'; goto backslash;
- case CTLESC: c = 'e'; goto backslash;
- case CTLVAR: c = 'v'; goto backslash;
- case CTLVAR+CTLQUOTE: c = 'V'; goto backslash;
- case CTLBACKQ: c = 'q'; goto backslash;
- case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash;
- backslash:
- putc('\\', tracefile);
- putc(c, tracefile);
- break;
- default:
- if (*p >= ' ' && *p <= '~')
- putc(*p, tracefile);
- else {
- putc('\\', tracefile);
- putc(*p >> 6 & 03, tracefile);
- putc(*p >> 3 & 07, tracefile);
- putc(*p & 07, tracefile);
+ INT_OFF;
+ rp = redirlist;
+ for (i = 0; i < 10; i++) {
+ if (rp->renamed[i] != EMPTY) {
+ if (!drop) {
+ close(i);
+ copyfd(rp->renamed[i], i);
}
- break;
+ close(rp->renamed[i]);
}
}
- putc('"', tracefile);
+ redirlist = rp->next;
+ nullredirs = rp->nullredirs;
+ free(rp);
+ INT_ON;
}
+/*
+ * Undo all redirections. Called on error or interrupt.
+ */
+/*
+ * Discard all saved file descriptors.
+ */
static void
-trargs(char **ap)
+clearredir(int drop)
{
- if (debug != 1)
- return;
- while (*ap) {
- trstring(*ap++);
- if (*ap)
- putc(' ', tracefile);
- else
- putc('\n', tracefile);
+ for (;;) {
+ nullredirs = 0;
+ if (!redirlist)
+ break;
+ popredir(drop);
}
}
-static void
-opentrace(void)
+/*
+ * Copy a file descriptor to be >= to. Returns -1
+ * if the source file descriptor is closed, EMPTY if there are no unused
+ * file descriptors left.
+ */
+static int
+copyfd(int from, int to)
{
- char s[100];
-#ifdef O_APPEND
- int flags;
-#endif
+ int newfd;
- if (debug != 1) {
- if (tracefile)
- fflush(tracefile);
- /* leave open because libedit might be using it */
- return;
+ newfd = fcntl(from, F_DUPFD, to);
+ if (newfd < 0) {
+ if (errno == EMFILE)
+ return EMPTY;
+ ash_msg_and_raise_error("%d: %m", from);
}
- scopy("./trace", s);
- if (tracefile) {
- if (!freopen(s, "a", tracefile)) {
- fprintf(stderr, "Can't re-open %s\n", s);
- debug = 0;
- return;
- }
- } else {
- tracefile = fopen(s, "a");
- if (tracefile == NULL) {
- fprintf(stderr, "Can't open %s\n", s);
- debug = 0;
- return;
- }
+ return newfd;
+}
+
+static int
+redirectsafe(union node *redir, int flags)
+{
+ int err;
+ volatile int saveint;
+ struct jmploc *volatile savehandler = exception_handler;
+ struct jmploc jmploc;
+
+ SAVE_INT(saveint);
+ err = setjmp(jmploc.loc) * 2;
+ if (!err) {
+ exception_handler = &jmploc;
+ redirect(redir, flags);
}
-#ifdef O_APPEND
- flags = fcntl(fileno(tracefile), F_GETFL, 0);
- if (flags >= 0)
- fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
-#endif
- setlinebuf(tracefile);
- fputs("\nTracing started.\n", tracefile);
+ exception_handler = savehandler;
+ if (err && exception != EXERROR)
+ longjmp(exception_handler->loc, 1);
+ RESTORE_INT(saveint);
+ return err;
}
-#endif /* DEBUG */
/* trap.c */
FORCE_INT_ON; /* enable interrupts */
if (s == 1)
goto state1;
- else if (s == 2)
+ if (s == 2)
goto state2;
- else if (s == 3)
+ if (s == 3)
goto state3;
- else
- goto state4;
+ goto state4;
}
exception_handler = &jmploc;
#if DEBUG
opentrace();
- trputs("Shell args: ");
- trargs(argv);
+ trace_puts("Shell args: ");
+ trace_puts_args(argv);
#endif
rootpid = getpid();