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