From fe1f00a7a7fe19e330ad9c610e21823b93bed8ed Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 23 Feb 2007 01:04:50 +0000 Subject: ash: cleanup part 4 --- shell/ash.c | 4212 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 2081 insertions(+), 2131 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 3107181..7ffecf4 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -65,36 +65,6 @@ #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 */ @@ -148,6 +118,13 @@ static char optlist[NOPTS]; /* ============ 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"; @@ -256,14 +233,16 @@ raise_interrupt(void) } #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) @@ -387,1794 +366,2135 @@ out2str(const char *p) } -/* ============ 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("\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); + } + 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, "", 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`", @@ -2736,7 +3056,7 @@ static int funcnest; /* depth of function calls */ */ #if JOBS -static int bgcmd(int, char **); +static int fg_bgcmd(int, char **); #endif static int breakcmd(int, char **); static int cdcmd(int, char **); @@ -2755,9 +3075,6 @@ static int execcmd(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 @@ -2840,7 +3157,7 @@ static const struct builtincmd builtincmd[] = { { 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 }, @@ -2858,7 +3175,7 @@ static const struct builtincmd builtincmd[] = { { 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 }, @@ -3033,25 +3350,12 @@ static void setjobctl(int); 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 */ @@ -3076,22 +3380,8 @@ static void clearredir(int); 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); @@ -3137,11 +3427,67 @@ static int is_safe_applet(char *name) #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) @@ -3205,16 +3551,6 @@ rmaliases(void) 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 */ @@ -3273,54 +3609,11 @@ unaliascmd(int argc, char **argv) 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 */ @@ -3602,7 +3895,7 @@ evalsubshell(union node *n, int flags) flags |= EV_EXIT; if (backgnd) flags &=~ EV_TESTED; -nofork: + nofork: redirect(n->nredir.redirect, 0); evaltreenr(n->nredir.n, flags); /* never returns */ @@ -4789,7 +5082,7 @@ cmdlookup(const char *name, int add) 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; } @@ -5292,7 +5585,7 @@ argstr(char *p, int flag) /* "$@" syntax adherence hack */ if ( !inquotes && - !memcmp(p, dolatstr, DOLATSTRLEN) && + !memcmp(p, dolatstr, 4) && (p[4] == CTLQUOTEMARK || ( p[4] == CTLENDVAR && p[5] == CTLQUOTEMARK @@ -6274,7 +6567,7 @@ expmeta(char *enddir, char *name) 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';) @@ -7186,7 +7479,7 @@ jobno(const struct job *jp) #if JOBS static int -fgcmd(int argc, char **argv) +fg_bgcmd(int argc, char **argv) { struct job *jp; FILE *out; @@ -7210,9 +7503,6 @@ fgcmd(int argc, char **argv) return retval; } -static int bgcmd(int, char **) __attribute__((__alias__("fgcmd"))); - - static int restartjob(struct job *jp, int mode) { @@ -7313,8 +7603,8 @@ showjob(FILE *out, 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 @@ -7359,19 +7649,20 @@ jobscmd(int argc, char **argv) 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; @@ -7515,7 +7806,8 @@ getjob(const char *name, int getctl) currentjob: err_msg = "No current job"; goto check; - } else if (c == '-') { + } + if (c == '-') { if (jp) jp = jp->prev_job; err_msg = "No previous job"; @@ -7571,7 +7863,6 @@ getjob(const char *name, int getctl) * Return a new job structure. * Called with interrupts off. */ - static struct job * makejob(union node *node, int nprocs) { @@ -7720,7 +8011,7 @@ static void forkchild(struct job *jp, union node *n, int mode) 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++; @@ -7735,7 +8026,7 @@ static void forkparent(struct job *jp, union node *n, int mode, pid_t pid) 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) { @@ -7854,7 +8145,8 @@ waitforjob(struct job *jp) * (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; @@ -8147,10 +8439,9 @@ cmdtxt(union node *n) s[0] = n->ndup.dupfd + '0'; p = s; goto dotail2; - } else { - n = n->nfile.fname; - goto donode; } + n = n->nfile.fname; + goto donode; } } @@ -8178,6 +8469,7 @@ cmdputs(const char *s) "", "}", "-", "+", "?", "=", "%", "%%", "#", "##" }; + nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); p = s; while ((c = *p++) != 0) { @@ -8192,11 +8484,10 @@ cmdputs(const char *s) 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); @@ -8650,7 +8941,7 @@ minus_o(char *name, int val) if (name) { for (i = 0; i < NOPTS; i++) { - if (equal(name, optnames(i))) { + if (strcmp(name, optnames(i)) == 0) { optlist[i] = val; return; } @@ -8859,13 +9150,15 @@ setcmd(int argc, char **argv) #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); @@ -8874,7 +9167,8 @@ static void change_lc_ctype(const char *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 */ @@ -10841,543 +11135,200 @@ openredirect(union node *redir) 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, "", 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("\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); - } - 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 */ @@ -13215,18 +13166,17 @@ int ash_main(int argc, char **argv) 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(); -- cgit v1.1