X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fash.c;h=f74fbd72f7b11d811730a34e12ee02253ce5e067;hb=488e609203c23b9826f75179f1b8e567617138ae;hp=bfdd94047bfdb57668f9ba88b72afd29071e59a1;hpb=4c179373e07fbc1d8fc8e53c7096fce9ee4b08b6;p=oweals%2Fbusybox.git diff --git a/shell/ash.c b/shell/ash.c index bfdd94047..f74fbd72f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -16,15 +16,14 @@ * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ //config:config ASH -//config: bool "ash" +//config: bool "ash (77 kb)" //config: default y //config: depends on !NOMMU //config: help -//config: Tha 'ash' shell adds about 60k in the default configuration and is -//config: the most complete and most pedantically correct shell included with -//config: busybox. This shell is actually a derivative of the Debian 'dash' -//config: shell (by Herbert Xu), which was created by porting the 'ash' shell -//config: (written by Kenneth Almquist) from NetBSD. +//config: The most complete and most pedantically correct shell included with +//config: busybox. This shell is actually a derivative of the Debian 'dash' +//config: shell (by Herbert Xu), which was created by porting the 'ash' shell +//config: (written by Kenneth Almquist) from NetBSD. //config: //config:# ash options //config:# note: Don't remove !NOMMU part in the next line; it would break @@ -41,11 +40,11 @@ //config: default y # Y is bigger, but because of uclibc glob() bug, let Y be default for now //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Do not use glob() function from libc, use internal implementation. -//config: Use this if you are getting "glob.h: No such file or directory" -//config: or similar build errors. -//config: Note that as of now (2017-01), uclibc and musl glob() both have bugs -//config: which would break ash if you select N here. +//config: Do not use glob() function from libc, use internal implementation. +//config: Use this if you are getting "glob.h: No such file or directory" +//config: or similar build errors. +//config: Note that as of now (2017-01), uclibc and musl glob() both have bugs +//config: which would break ash if you select N here. //config: //config:config ASH_BASH_COMPAT //config: bool "bash-compatible extensions" @@ -67,37 +66,37 @@ //config: default y //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable pseudorandom generator and dynamic variable "$RANDOM". -//config: Each read of "$RANDOM" will generate a new pseudorandom value. -//config: You can reset the generator by using a specified start value. -//config: After "unset RANDOM" the generator will switch off and this -//config: variable will no longer have special treatment. +//config: Enable pseudorandom generator and dynamic variable "$RANDOM". +//config: Each read of "$RANDOM" will generate a new pseudorandom value. +//config: You can reset the generator by using a specified start value. +//config: After "unset RANDOM" the generator will switch off and this +//config: variable will no longer have special treatment. //config: //config:config ASH_EXPAND_PRMT //config: bool "Expand prompt string" //config: default y //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: $PS# may contain volatile content, such as backquote commands. -//config: This option recreates the prompt string from the environment -//config: variable each time it is displayed. +//config: $PS# may contain volatile content, such as backquote commands. +//config: This option recreates the prompt string from the environment +//config: variable each time it is displayed. //config: //config:config ASH_IDLE_TIMEOUT //config: bool "Idle timeout variable $TMOUT" //config: default y //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable bash-like auto-logout after $TMOUT seconds of idle time. +//config: Enable bash-like auto-logout after $TMOUT seconds of idle time. //config: //config:config ASH_MAIL //config: bool "Check for new mail in interactive shell" //config: default y //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable "check for new mail" function: -//config: if set, $MAIL file and $MAILPATH list of files -//config: are checked for mtime changes, and "you have mail" -//config: message is printed if change is detected. +//config: Enable "check for new mail" function: +//config: if set, $MAIL file and $MAILPATH list of files +//config: are checked for mtime changes, and "you have mail" +//config: message is printed if change is detected. //config: //config:config ASH_ECHO //config: bool "echo builtin" @@ -129,14 +128,15 @@ //config: default y //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable support for the 'command' builtin, which allows -//config: you to run the specified command or builtin, -//config: even when there is a function with the same name. +//config: Enable support for the 'command' builtin, which allows +//config: you to run the specified command or builtin, +//config: even when there is a function with the same name. //config: //config:endif # ash options //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP)) -//applet:IF_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, ash)) +// APPLET_ODDNAME:name main location suid_type help +//applet:IF_SH_IS_ASH( APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, ash)) //applet:IF_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, ash)) //kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o @@ -214,6 +214,9 @@ #include "shell_common.h" #if ENABLE_FEATURE_SH_MATH # include "math.h" +#else +typedef long arith_t; +# define ARITH_FMT "%ld" #endif #if ENABLE_ASH_RANDOM_SUPPORT # include "random.h" @@ -621,8 +624,8 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...) va_list ap; int ret; - va_start(ap, fmt); INT_OFF; + va_start(ap, fmt); ret = vsnprintf(outbuf, length, fmt, ap); va_end(ap); INT_ON; @@ -1247,7 +1250,6 @@ static struct parsefile basepf; /* top level input file */ static struct parsefile *g_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 */ /* ============ Message printing */ @@ -1658,7 +1660,7 @@ static char * stack_nputstr(const char *s, size_t n, char *p) { p = makestrspace(n, p); - p = (char *)memcpy(p, s, n) + n; + p = (char *)mempcpy(p, s, n); return p; } @@ -1742,7 +1744,7 @@ number(const char *s) } /* - * Produce a possibly single quoted string suitable as input to the shell. + * Produce a single quoted string suitable as input to the shell. * The return string is allocated on the stack. */ static char * @@ -1761,7 +1763,7 @@ single_quote(const char *s) q = p = makestrspace(len + 3, p); *q++ = '\''; - q = (char *)memcpy(q, s, len) + len; + q = (char *)mempcpy(q, s, len); *q++ = '\''; s += len; @@ -1775,7 +1777,7 @@ single_quote(const char *s) q = p = makestrspace(len + 3, p); *q++ = '"'; - q = (char *)memcpy(q, s - len, len) + len; + q = (char *)mempcpy(q, s - len, len); *q++ = '"'; STADJUST(q - p, p); @@ -1786,6 +1788,44 @@ single_quote(const char *s) return stackblock(); } +/* + * Produce a possibly single quoted string suitable as input to the shell. + * If quoting was done, the return string is allocated on the stack, + * otherwise a pointer to the original string is returned. + */ +static const char * +maybe_single_quote(const char *s) +{ + const char *p = s; + + while (*p) { + /* Assuming ACSII */ + /* quote ctrl_chars space !"#$%&'()* */ + if (*p < '+') + goto need_quoting; + /* quote ;<=>? */ + if (*p >= ';' && *p <= '?') + goto need_quoting; + /* quote `[\ */ + if (*p == '`') + goto need_quoting; + if (*p == '[') + goto need_quoting; + if (*p == '\\') + goto need_quoting; + /* quote {|}~ DEL and high bytes */ + if (*p > 'z') + goto need_quoting; + /* Not quoting these: +,-./ 0-9 :@ A-Z ]^_ a-z */ + /* TODO: maybe avoid quoting % */ + p++; + } + return s; + + need_quoting: + return single_quote(s); +} + /* ============ nextopt */ @@ -2184,15 +2224,9 @@ reinit_unicode_for_ash(void) /* * Search the environment of a builtin command. */ -static const char * +static ALWAYS_INLINE const char * bltinlookup(const char *name) { - struct strlist *sp; - - for (sp = cmdenviron; sp; sp = sp->next) { - if (varcmp(sp->text, name) == 0) - return var_end(sp->text); - } return lookupvar(name); } @@ -2203,14 +2237,15 @@ bltinlookup(const char *name) * will go away. * Called with interrupts off. */ -static void +static struct var * setvareq(char *s, int flags) { struct var *vp, **vpp; vpp = hashvar(s); flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); - vp = *findvar(vpp, s); + vpp = findvar(vpp, s); + vp = *vpp; if (vp) { if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { const char *n; @@ -2223,7 +2258,7 @@ setvareq(char *s, int flags) } if (flags & VNOSET) - return; + goto out; if (vp->var_func && !(flags & VNOFUNC)) vp->var_func(var_end(s)); @@ -2231,11 +2266,22 @@ setvareq(char *s, int flags) if (!(vp->flags & (VTEXTFIXED|VSTACK))) free((char*)vp->var_text); + if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | (vp->flags & VSTRFIXED)) == VUNSET) { + *vpp = vp->next; + free(vp); + out_free: + if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE) + free(s); + goto out; + } + flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); } else { /* variable s is not found */ if (flags & VNOSET) - return; + goto out; + if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET) + goto out_free; vp = ckzalloc(sizeof(*vp)); vp->next = *vpp; /*vp->func = NULL; - ckzalloc did it */ @@ -2245,13 +2291,16 @@ setvareq(char *s, int flags) s = ckstrdup(s); vp->var_text = s; vp->flags = flags; + + out: + return vp; } /* * 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 +static struct var * setvar(const char *name, const char *val, int flags) { const char *q; @@ -2259,6 +2308,7 @@ setvar(const char *name, const char *val, int flags) char *nameeq; size_t namelen; size_t vallen; + struct var *vp; q = endofname(name); p = strchrnul(q, '='); @@ -2274,14 +2324,16 @@ setvar(const char *name, const char *val, int flags) INT_OFF; nameeq = ckmalloc(namelen + vallen + 2); - p = memcpy(nameeq, name, namelen) + namelen; + p = mempcpy(nameeq, name, namelen); if (val) { *p++ = '='; - p = memcpy(p, val, vallen) + vallen; + p = mempcpy(p, val, vallen); } *p = '\0'; - setvareq(nameeq, flags | VNOSAVE); + vp = setvareq(nameeq, flags | VNOSAVE); INT_ON; + + return vp; } static void FAST_FUNC @@ -2293,43 +2345,10 @@ setvar0(const char *name, const char *val) /* * Unset the specified variable. */ -static int +static void 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; - - retval = 1; - if (flags & VREADONLY) - goto out; -#if ENABLE_ASH_RANDOM_SUPPORT - vp->flags &= ~VDYNAMIC; -#endif - if (flags & VUNSET) - goto ok; - if ((flags & VSTRFIXED) == 0) { - INT_OFF; - if ((flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->var_text); - *vpp = vp->next; - free(vp); - INT_ON; - } else { - setvar0(s, NULL); - vp->flags &= ~VEXPORT; - } - ok: - retval = 0; - } - out: - return retval; + setvar(s, NULL, 0); } /* @@ -2412,8 +2431,7 @@ path_advance(const char **path, const char *name) growstackblock(); q = stackblock(); if (p != start) { - memcpy(q, start, p - start); - q += p - start; + q = mempcpy(q, start, p - start); *q++ = '/'; } strcpy(q, name); @@ -2457,12 +2475,8 @@ putprompt(const char *s) } #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 static void setprompt_if(smallint do_set, int whichprompt) @@ -2487,10 +2501,10 @@ setprompt_if(smallint do_set, int whichprompt) } #if ENABLE_ASH_EXPAND_PRMT pushstackmark(&smark, stackblocksize()); -#endif putprompt(expandstr(prompt)); -#if ENABLE_ASH_EXPAND_PRMT popstackmark(&smark); +#else + putprompt(prompt); #endif } @@ -3344,11 +3358,9 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { int i; - while ((i = nextopt("a")) != '\0') { - if (i == 'a') { - rmaliases(); - return 0; - } + while (nextopt("a") != '\0') { + rmaliases(); + return 0; } for (i = 0; *argptr; argptr++) { if (unalias(*argptr)) { @@ -3579,6 +3591,72 @@ static struct job *curjob; //lots /* number of presumed living untracked jobs */ static int jobless; //4 +#if 0 +/* Bash has a feature: it restores termios after a successful wait for + * a foreground job which had at least one stopped or sigkilled member. + * The probable rationale is that SIGSTOP and SIGKILL can preclude task from + * properly restoring tty state. Should we do this too? + * A reproducer: ^Z an interactive python: + * + * # python + * Python 2.7.12 (...) + * >>> ^Z + * { python leaves tty in -icanon -echo state. We do survive that... } + * [1]+ Stopped python + * { ...however, next program (python #2) does not survive it well: } + * # python + * Python 2.7.12 (...) + * >>> Traceback (most recent call last): + * { above, I typed "qwerty", but -echo state is still in effect } + * File "", line 1, in + * NameError: name 'qwerty' is not defined + * + * The implementation below is modeled on bash code and seems to work. + * However, I'm not sure we should do this. For one: what if I'd fg + * the stopped python instead? It'll be confused by "restored" tty state. + */ +static struct termios shell_tty_info; +static void +get_tty_state(void) +{ + if (rootshell && ttyfd >= 0) + tcgetattr(ttyfd, &shell_tty_info); +} +static void +set_tty_state(void) +{ + /* if (rootshell) - caller ensures this */ + if (ttyfd >= 0) + tcsetattr(ttyfd, TCSADRAIN, &shell_tty_info); +} +static int +job_signal_status(struct job *jp) +{ + int status; + unsigned i; + struct procstat *ps = jp->ps; + for (i = 0; i < jp->nprocs; i++) { + status = ps[i].ps_status; + if (WIFSIGNALED(status) || WIFSTOPPED(status)) + return status; + } + return 0; +} +static void +restore_tty_if_stopped_or_signaled(struct job *jp) +{ +//TODO: check what happens if we come from waitforjob() in expbackq() + if (rootshell) { + int s = job_signal_status(jp); + if (s) /* WIFSIGNALED(s) || WIFSTOPPED(s) */ + set_tty_state(); + } +} +#else +# define get_tty_state() ((void)0) +# define restore_tty_if_stopped_or_signaled(jp) ((void)0) +#endif + static void set_curjob(struct job *jp, unsigned mode) { @@ -3784,7 +3862,7 @@ setjobctl(int on) } /* fd is a tty at this point */ fd = fcntl(fd, F_DUPFD, 10); - if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, dont */ + if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, don't */ close(ofd); if (fd < 0) goto out; /* F_DUPFD failed */ @@ -3910,8 +3988,10 @@ restartjob(struct job *jp, int mode) goto out; jp->state = JOBRUNNING; pgid = jp->ps[0].ps_pid; - if (mode == FORK_FG) + if (mode == FORK_FG) { + get_tty_state(); xtcsetpgrp(ttyfd, pgid); + } killpg(pgid, SIGCONT); ps = jp->ps; i = jp->nprocs; @@ -3959,15 +4039,19 @@ sprint_status48(char *s, int status, int sigonly) col = 0; if (!WIFEXITED(status)) { - if (JOBS && WIFSTOPPED(status)) +#if JOBS + if (WIFSTOPPED(status)) st = WSTOPSIG(status); else +#endif st = WTERMSIG(status); if (sigonly) { if (st == SIGINT || st == SIGPIPE) goto out; - if (JOBS && WIFSTOPPED(status)) +#if JOBS + if (WIFSTOPPED(status)) goto out; +#endif } st &= 0x7f; //TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata @@ -4119,8 +4203,10 @@ dowait(int block, struct job *job) goto out; } /* The process wasn't found in job list */ - if (JOBS && !WIFSTOPPED(status)) +#if JOBS + if (!WIFSTOPPED(status)) jobless--; +#endif out: INT_ON; @@ -4445,7 +4531,7 @@ makejob(/*union node *node,*/ int nprocs) memset(jp, 0, sizeof(*jp)); #if JOBS /* jp->jobctl is a bitfield. - * "jp->jobctl |= jobctl" likely to give awful code */ + * "jp->jobctl |= doing_jobctl" likely to give awful code */ if (doing_jobctl) jp->jobctl = 1; #endif @@ -5040,6 +5126,8 @@ waitforjob(struct job *jp) #if JOBS if (jp->jobctl) { xtcsetpgrp(ttyfd, rootpid); + restore_tty_if_stopped_or_signaled(jp); + /* * This is truly gross. * If we're doing job control, then we did a TIOCSPGRP which @@ -5089,68 +5177,6 @@ stoppedjobs(void) #define EMPTY -2 /* marks an unused slot in redirtab */ #define CLOSED -3 /* marks a slot of previously-closed fd */ -/* - * Open a file in noclobber mode. - * The code was copied from bash. - */ -static int -noclobberopen(const char *fname) -{ - int r, fd; - struct stat finfo, finfo2; - - /* - * If the file exists and is a regular file, return an error - * immediately. - */ - r = stat(fname, &finfo); - if (r == 0 && S_ISREG(finfo.st_mode)) { - errno = EEXIST; - return -1; - } - - /* - * If the file was not present (r != 0), make sure we open it - * exclusively so that if it is created before we open it, our open - * will fail. Make sure that we do not truncate an existing file. - * Note that we don't turn on O_EXCL unless the stat failed -- if the - * file was not a regular file, we leave O_EXCL off. - */ - if (r != 0) - return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); - fd = open(fname, O_WRONLY|O_CREAT, 0666); - - /* If the open failed, return the file descriptor right away. */ - if (fd < 0) - return fd; - - /* - * OK, the open succeeded, but the file may have been changed from a - * non-regular file to a regular file between the stat and the open. - * We are assuming that the O_EXCL open handles the case where FILENAME - * did not exist and is symlinked to an existing file between the stat - * and open. - */ - - /* - * If we can open it and fstat the file descriptor, and neither check - * revealed that it was a regular file, and the file has not been - * replaced, return the file descriptor. - */ - if (fstat(fd, &finfo2) == 0 - && !S_ISREG(finfo2.st_mode) - && finfo.st_dev == finfo2.st_dev - && finfo.st_ino == finfo2.st_ino - ) { - return fd; - } - - /* The file has been replaced. badness. */ - close(fd); - errno = EEXIST; - return -1; -} - /* * Handle here documents. Normally we fork off a process to write the * data to a pipe. If the document is short, we can stuff the data in @@ -5195,6 +5221,7 @@ openhere(union node *redir) static int openredirect(union node *redir) { + struct stat sb; char *fname; int f; @@ -5234,9 +5261,23 @@ openredirect(union node *redir) #endif /* Take care of noclobber mode. */ if (Cflag) { - f = noclobberopen(fname); - if (f < 0) + if (stat(fname, &sb) < 0) { + f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + if (f < 0) + goto ecreate; + } else if (!S_ISREG(sb.st_mode)) { + f = open(fname, O_WRONLY, 0666); + if (f < 0) + goto ecreate; + if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) { + close(f); + errno = EEXIST; + goto ecreate; + } + } else { + errno = EEXIST; goto ecreate; + } break; } /* FALLTHROUGH */ @@ -5626,7 +5667,7 @@ ash_arith(const char *s) #define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */ /* Add CTLESC when necessary. */ -#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR) +#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) /* Do not skip NUL characters. */ #define QUOTES_KEEPNUL EXP_TILDE @@ -5659,19 +5700,20 @@ static struct arglist exparg; /* * Our own itoa(). + * cvtnum() is used even if math support is off (to prepare $? values and such). */ -#if !ENABLE_FEATURE_SH_MATH -/* cvtnum() is used even if math support is off (to prepare $? values and such) */ -typedef long arith_t; -# define ARITH_FMT "%ld" -#endif static int cvtnum(arith_t num) { int len; - expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest); - len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num); + /* 32-bit and wider ints require buffer size of bytes*3 (or less) */ + len = sizeof(arith_t) * 3; + /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127" */ + if (sizeof(arith_t) < 4) len += 2; + + expdest = makestrspace(len, expdest); + len = fmtstr(expdest, len, ARITH_FMT, num); STADJUST(len, expdest); return len; } @@ -5838,7 +5880,7 @@ rmescapes(char *str, int flag) } q = r; if (len > 0) { - q = (char *)memcpy(q, str, len) + len; + q = (char *)mempcpy(q, str, len); } } @@ -5848,6 +5890,7 @@ rmescapes(char *str, int flag) while (*p) { if ((unsigned char)*p == CTLQUOTEMARK) { // Note: both inquotes and protect_against_glob only affect whether +// CTLESC, gets converted to or to \ inquotes = ~inquotes; p++; protect_against_glob = globbing; @@ -5860,7 +5903,33 @@ rmescapes(char *str, int flag) ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)"); #endif if (protect_against_glob) { - *q++ = '\\'; + /* + * We used to trust glob() and fnmatch() to eat + * superfluous escapes (\z where z has no + * special meaning anyway). But this causes + * bugs such as string of one greek letter rho + * (unicode-encoded as two bytes "cf,81") + * getting encoded as "cf,CTLESC,81" + * and here, converted to "cf,\,81" - + * which does not go well with some flavors + * of fnmatch() in unicode locales + * (for example, glibc <= 2.22). + * + * Lets add "\" only on the chars which need it. + * Testcases for less obvious chars are shown. + */ + if (*p == '*' + || *p == '?' + || *p == '[' + || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */ + || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */ + || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */ + || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */ + /* Some libc support [^negate], that's why "^" also needs love */ + || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */ + ) { + *q++ = '\\'; + } } } else if (*p == '\\' && !inquotes) { /* naked back slash */ @@ -6063,7 +6132,9 @@ struct backcmd { /* result of evalbackcmd */ }; /* These forward decls are needed to use "eval" code for backticks handling: */ -#define EV_EXIT 01 /* exit after evaluating tree */ +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ static int evaltree(union node *, int); static void FAST_FUNC @@ -6233,19 +6304,15 @@ expari(int flag) #endif /* argstr needs it */ -static char *evalvar(char *p, int flags, struct strlist *var_str_list); +static char *evalvar(char *p, int flags); /* * Perform variable and command substitution. If EXP_FULL is set, output CTLESC * characters to allow for further processing. Otherwise treat * $@ like $* since no splitting will be performed. - * - * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence - * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it - * for correct expansion of "B=$A" word. */ static void -argstr(char *p, int flags, struct strlist *var_str_list) +argstr(char *p, int flags) { static const char spclchars[] ALIGN1 = { '=', @@ -6338,7 +6405,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) inquotes ^= EXP_QUOTED; /* "$@" syntax adherence hack */ if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { - p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1; + p = evalvar(p + 1, flags | inquotes) + 1; goto start; } addquote: @@ -6364,7 +6431,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) goto addquote; case CTLVAR: TRACE(("argstr: evalvar('%s')\n", p)); - p = evalvar(p, flags | inquotes, var_str_list); + p = evalvar(p, flags | inquotes); TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock())); goto start; case CTLBACKQ: @@ -6451,8 +6518,8 @@ scanright(char *startp, char *rmesc, char *rmescend, if (try2optimize) { /* Maybe we can optimize this: * if pattern ends with unescaped *, we can avoid checking - * shorter strings: if "foo*" doesnt match "raw_value_of_v", - * it wont match truncated "raw_value_of_" strings too. + * shorter strings: if "foo*" doesn't match "raw_value_of_v", + * it won't match truncated "raw_value_of_" strings too. */ unsigned plen = strlen(pattern); /* Does it end with "*"? */ @@ -6506,7 +6573,7 @@ varunset(const char *end, const char *var, const char *umsg, int varflags) static const char * subevalvar(char *p, char *varname, int strloc, int subtype, - int startloc, int varflags, int flag, struct strlist *var_str_list) + int startloc, int varflags, int flag) { struct nodelist *saveargbackq = argbackq; int quotes = flag & QUOTES_ESC; @@ -6514,7 +6581,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype, char *loc; char *rmesc, *rmescend; char *str; - IF_BASH_SUBSTR(int pos, len, orig_len;) int amount, resetloc; IF_BASH_PATTERN_SUBST(int workloc;) IF_BASH_PATTERN_SUBST(char *repl = NULL;) @@ -6525,8 +6591,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype, // p, varname, strloc, subtype, startloc, varflags, quotes); argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? - (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0), - var_str_list); + (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0) + ); STPUTC('\0', expdest); argbackq = saveargbackq; startp = (char *)stackblock() + startloc; @@ -6543,14 +6609,23 @@ subevalvar(char *p, char *varname, int strloc, int subtype, /* NOTREACHED */ #if BASH_SUBSTR - case VSSUBSTR: -//TODO: support more general format ${v:EXPR:EXPR}, -// where EXPR follows $(()) rules + case VSSUBSTR: { + int pos, len, orig_len; + char *colon; + loc = str = stackblock() + strloc; + +# if !ENABLE_FEATURE_SH_MATH +# define ash_arith number +# endif /* Read POS in ${var:POS:LEN} */ - pos = atoi(loc); /* number(loc) errors out on "1:4" */ - len = str - startp - 1; + colon = strchr(loc, ':'); + if (colon) *colon = '\0'; + pos = ash_arith(loc); + if (colon) *colon = ':'; + /* Read LEN in ${var:POS:LEN} */ + len = str - startp - 1; /* *loc != '\0', guaranteed by parser */ if (quotes) { char *ptr; @@ -6564,25 +6639,21 @@ subevalvar(char *p, char *varname, int strloc, int subtype, } } orig_len = len; - if (*loc++ == ':') { /* ${var::LEN} */ - len = number(loc); + len = ash_arith(loc); } else { /* Skip POS in ${var:POS:LEN} */ len = orig_len; while (*loc && *loc != ':') { - /* TODO? - * bash complains on: var=qwe; echo ${var:1a:123} - if (!isdigit(*loc)) - ash_msg_and_raise_error(msg_illnum, str); - */ loc++; } if (*loc++ == ':') { - len = number(loc); + len = ash_arith(loc); } } +# undef ash_arith + if (pos < 0) { /* ${VAR:$((-n)):l} starts n chars from the end */ pos = orig_len + pos; @@ -6590,12 +6661,16 @@ subevalvar(char *p, char *varname, int strloc, int subtype, if ((unsigned)pos >= orig_len) { /* apart from obvious ${VAR:999999:l}, * covers ${VAR:$((-9999999)):l} - result is "" - * (bash-compat) + * (bash compat) */ pos = 0; len = 0; } - if (len > (orig_len - pos)) + if (len < 0) { + /* ${VAR:N:-M} sets LEN to strlen()-M */ + len = (orig_len - pos) + len; + } + if ((unsigned)len > (orig_len - pos)) len = orig_len - pos; for (str = startp; pos; str++, pos--) { @@ -6611,6 +6686,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, amount = loc - expdest; STADJUST(amount, expdest); return loc; + } #endif /* BASH_SUBSTR */ } @@ -6655,6 +6731,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, #if BASH_PATTERN_SUBST workloc = expdest - (char *)stackblock(); if (subtype == VSREPLACE || subtype == VSREPLACEALL) { + int len; char *idx, *end; if (!repl) { @@ -6792,7 +6869,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, * ash -c 'echo ${#1#}' name:'1=#' */ static NOINLINE ssize_t -varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp) +varvalue(char *name, int varflags, int flags, int *quotedp) { const char *p; int num; @@ -6884,31 +6961,6 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int goto value; default: /* NB: name has form "VAR=..." */ - - /* "A=a B=$A" case: var_str_list is a list of "A=a" strings - * which should be considered before we check variables. */ - if (var_str_list) { - unsigned name_len = (strchrnul(name, '=') - name) + 1; - p = NULL; - do { - char *str, *eq; - str = var_str_list->text; - eq = strchr(str, '='); - if (!eq) /* stop at first non-assignment */ - break; - eq++; - if (name_len == (unsigned)(eq - str) - && strncmp(str, name, name_len) == 0 - ) { - p = eq; - /* goto value; - WRONG! */ - /* think "A=1 A=2 B=$A" */ - } - var_str_list = var_str_list->next; - } while (var_str_list); - if (p) - goto value; - } p = lookupvar(name); value: if (!p) @@ -6938,7 +6990,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int * input string. */ static char * -evalvar(char *p, int flag, struct strlist *var_str_list) +evalvar(char *p, int flag) { char varflags; char subtype; @@ -6962,7 +7014,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) p = strchr(p, '=') + 1; //TODO: use var_end(p)? again: - varlen = varvalue(var, varflags, flag, var_str_list, "ed); + varlen = varvalue(var, varflags, flag, "ed); if (varflags & VSNUL) varlen--; @@ -6976,8 +7028,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) if (varlen < 0) { argstr( p, - flag | EXP_TILDE | EXP_WORD, - var_str_list + flag | EXP_TILDE | EXP_WORD ); goto end; } @@ -6989,7 +7040,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) goto record; subevalvar(p, var, 0, subtype, startloc, varflags, - flag & ~QUOTES_ESC, var_str_list); + flag & ~QUOTES_ESC); varflags &= ~VSNUL; /* * Remove any recorded regions beyond @@ -7042,7 +7093,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) STPUTC('\0', expdest); patloc = expdest - (char *)stackblock(); if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype, - startloc, varflags, flag, var_str_list)) { + startloc, varflags, flag)) { int amount = expdest - ( (char *)stackblock() + patloc - 1 ); @@ -7089,6 +7140,57 @@ addfname(const char *name) exparg.lastp = &sp->next; } +/* Avoid glob() (and thus, stat() et al) for words like "echo" */ +static int +hasmeta(const char *p) +{ + static const char chars[] ALIGN1 = { + '*', '?', '[', '\\', CTLQUOTEMARK, CTLESC, 0 + }; + + for (;;) { + p = strpbrk(p, chars); + if (!p) + break; + switch ((unsigned char) *p) { + case CTLQUOTEMARK: + for (;;) { + p++; + if (*p == CTLQUOTEMARK) + break; + if (*p == CTLESC) + p++; + if (*p == '\0') /* huh? */ + return 0; + } + break; + case '\\': + case CTLESC: + p++; + if (*p == '\0') + return 0; + break; + case '[': + if (!strchr(p + 1, ']')) { + /* It's not a properly closed [] pattern, + * but other metas may follow. Continue checking. + * my[file* _is_ globbed by bash + * and matches filenames like "my[file1". + */ + break; + } + /* fallthrough */ + default: + /* case '*': */ + /* case '?': */ + return 1; + } + p++; + } + + return 0; +} + /* If we want to use glob() from libc... */ #if !ENABLE_ASH_INTERNAL_GLOB @@ -7115,20 +7217,9 @@ expandmeta(struct strlist *str /*, int flag*/) if (fflag) goto nometa; - /* Avoid glob() (and thus, stat() et al) for words like "echo" */ - p = str->text; - while (*p) { - if (*p == '*') - goto need_glob; - if (*p == '?') - goto need_glob; - if (*p == '[') - goto need_glob; - p++; - } - goto nometa; + if (!hasmeta(str->text)) + goto nometa; - need_glob: INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match @@ -7139,7 +7230,7 @@ expandmeta(struct strlist *str /*, int flag*/) // Which means you need to unescape the string, right? Not so fast: // if there _is_ a file named "file\?" (with backslash), it is returned // as "file\?" too (whichever pattern you used to find it, say, "file*"). -// You DONT KNOW by looking at the result whether you need to unescape it. +// You DON'T KNOW by looking at the result whether you need to unescape it. // // Worse, globbing of "file\?" in a directory with two files, "file?" and "file\?", // returns "file\?" - which is WRONG: "file\?" pattern matches "file?" file. @@ -7365,9 +7456,6 @@ expsort(struct strlist *str) static void expandmeta(struct strlist *str /*, int flag*/) { - static const char metachars[] ALIGN1 = { - '*', '?', '[', 0 - }; /* TODO - EXP_REDIR */ while (str) { @@ -7378,7 +7466,7 @@ expandmeta(struct strlist *str /*, int flag*/) if (fflag) goto nometa; - if (!strpbrk(str->text, metachars)) + if (!hasmeta(str->text)) goto nometa; savelastp = exparg.lastp; @@ -7429,8 +7517,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag) argbackq = arg->narg.backquote; STARTSTACKSTR(expdest); TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag)); - argstr(arg->narg.text, flag, - /* var_str_list: */ arglist ? arglist->list : NULL); + argstr(arg->narg.text, flag); p = _STPUTC('\0', expdest); expdest = p - 1; if (arglist == NULL) { @@ -7449,10 +7536,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag) exparg.lastp = &exparg.list; expandmeta(exparg.list /*, flag*/); } else { - if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */ - rmescapes(p, 0); - TRACE(("expandarg: rmescapes:'%s'\n", p)); - } sp = stzalloc(sizeof(*sp)); sp->text = p; *exparg.lastp = sp; @@ -7484,7 +7567,9 @@ expandhere(union node *arg, int fd) static int patmatch(char *pattern, const char *string) { - return pmatch(preglob(pattern, 0), string); + char *p = preglob(pattern, 0); + //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string); + return pmatch(p, string); } /* @@ -7499,8 +7584,7 @@ casematch(union node *pattern, char *val) setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); - argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, - /* var_str_list: */ NULL); + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); STACKSTRNUL(expdest); ifsfree(); result = patmatch(stackblock(), val); @@ -7586,7 +7670,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** clearenv(); while (*envp) putenv(*envp++); - run_applet_no_and_exit(applet_no, argv); + popredir(/*drop:*/ 1, /*restore:*/ 0); + run_applet_no_and_exit(applet_no, cmd, argv); } /* re-exec ourselves with the new arguments */ execve(bb_busybox_exec_path, argv, envp); @@ -7636,9 +7721,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** * have to change the find_command routine as well. * argv[-1] must exist and be writable! See tryexec() for why. */ -static void shellexec(char **, const char *, int) NORETURN; -static void -shellexec(char **argv, const char *path, int idx) +static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN; +static void shellexec(char *prog, char **argv, const char *path, int idx) { char *cmdname; int e; @@ -7647,12 +7731,12 @@ shellexec(char **argv, const char *path, int idx) int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */ envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL); - if (strchr(argv[0], '/') != NULL + if (strchr(prog, '/') != NULL #if ENABLE_FEATURE_SH_STANDALONE - || (applet_no = find_applet_by_name(argv[0])) >= 0 + || (applet_no = find_applet_by_name(prog)) >= 0 #endif ) { - tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp); + tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) prog, argv, envp); if (applet_no >= 0) { /* We tried execing ourself, but it didn't work. * Maybe /proc/self/exe doesn't exist? @@ -7664,7 +7748,7 @@ shellexec(char **argv, const char *path, int idx) } else { try_PATH: e = ENOENT; - while ((cmdname = path_advance(&path, argv[0])) != NULL) { + while ((cmdname = path_advance(&path, prog)) != NULL) { if (--idx < 0 && pathopt == NULL) { tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); if (errno != ENOENT && errno != ENOTDIR) @@ -7688,8 +7772,8 @@ shellexec(char **argv, const char *path, int idx) } exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", - argv[0], e, suppress_int)); - ash_msg_and_raise(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found")); + prog, e, suppress_int)); + ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found")); /* NOTREACHED */ } @@ -8058,7 +8142,6 @@ static int describe_command(char *command, const char *path, int describe_command_verbose) { struct cmdentry entry; - struct tblentry *cmdp; #if ENABLE_ASH_ALIAS const struct alias *ap; #endif @@ -8088,15 +8171,8 @@ describe_command(char *command, const char *path, int describe_command_verbose) goto out; } #endif - /* Then check if it is a tracked alias */ - cmdp = cmdlookup(command, 0); - if (cmdp != NULL) { - entry.cmdtype = cmdp->cmdtype; - entry.u = cmdp->param; - } else { - /* Finally use brute force */ - find_command(command, &entry, DO_ABS, path); - } + /* Brute force */ + find_command(command, &entry, DO_ABS, path); switch (entry.cmdtype) { case CMDNORMAL: { @@ -8111,9 +8187,7 @@ describe_command(char *command, const char *path, int describe_command_verbose) } while (--j >= 0); } if (describe_command_verbose) { - out1fmt(" is%s %s", - (cmdp ? " a tracked alias for" : nullstr), p - ); + out1fmt(" is %s", p); } else { out1str(p); } @@ -8246,10 +8320,6 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) static void *funcblock; /* block to allocate function from */ static char *funcstring_end; /* end of block to allocate strings from */ -/* flags in argument to evaltree */ -#define EV_EXIT 01 /* exit after evaluating tree */ -#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ - static const uint8_t nodesize[N_NUMBER] ALIGN1 = { [NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)), [NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)), @@ -8852,13 +8922,15 @@ static int evalsubshell(union node *n, int flags) { struct job *jp; - int backgnd = (n->type == NBACKGND); + int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */ int status; expredir(n->nredir.redirect); if (!backgnd && (flags & EV_EXIT) && !may_have_traps) goto nofork; INT_OFF; + if (backgnd == FORK_FG) + get_tty_state(); jp = makejob(/*n,*/ 1); if (forkshell(jp, n, backgnd) == 0) { /* child */ @@ -8873,7 +8945,7 @@ evalsubshell(union node *n, int flags) } /* parent */ status = 0; - if (!backgnd) + if (backgnd == FORK_FG) status = waitforjob(jp); INT_ON; return status; @@ -8965,6 +9037,8 @@ evalpipe(union node *n, int flags) pipelen++; flags |= EV_EXIT; INT_OFF; + if (n->npipe.pipe_backgnd == 0) + get_tty_state(); jp = makejob(/*n,*/ pipelen); prevfd = -1; for (lp = n->npipe.cmdlist; lp; lp = lp->next) { @@ -9061,27 +9135,57 @@ optschanged(void) #endif } -static struct localvar *localvars; +struct localvar_list { + struct localvar_list *next; + struct localvar *lv; +}; + +static struct localvar_list *localvar_stack; /* * Called after a function returns. * Interrupts must be off. */ static void -poplocalvars(void) +poplocalvars(int keep) { - struct localvar *lvp; + struct localvar_list *ll; + struct localvar *lvp, *next; struct var *vp; - while ((lvp = localvars) != NULL) { - localvars = lvp->next; + INT_OFF; + ll = localvar_stack; + localvar_stack = ll->next; + + next = ll->lv; + free(ll); + + while ((lvp = next) != NULL) { + next = lvp->next; vp = lvp->vp; TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-")); - if (vp == NULL) { /* $- saved */ + if (keep) { + int bits = VSTRFIXED; + + if (lvp->flags != VUNSET) { + if (vp->var_text == lvp->text) + bits |= VTEXTFIXED; + else if (!(lvp->flags & (VTEXTFIXED|VSTACK))) + free((char*)lvp->text); + } + + vp->flags &= ~bits; + vp->flags |= (lvp->flags & bits); + + if ((vp->flags & + (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET) + unsetvar(vp->var_text); + } else if (vp == NULL) { /* $- saved */ memcpy(optlist, lvp->text, sizeof(optlist)); free((char*)lvp->text); optschanged(); - } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + } else if (lvp->flags == VUNSET) { + vp->flags &= ~(VSTRFIXED|VREADONLY); unsetvar(vp->var_text); } else { if (vp->var_func) @@ -9093,19 +9197,43 @@ poplocalvars(void) } free(lvp); } + INT_ON; +} + +/* + * Create a new localvar environment. + */ +static struct localvar_list * +pushlocalvars(void) +{ + struct localvar_list *ll; + + INT_OFF; + ll = ckzalloc(sizeof(*ll)); + /*ll->lv = NULL; - zalloc did it */ + ll->next = localvar_stack; + localvar_stack = ll; + INT_ON; + + return ll->next; +} + +static void +unwindlocalvars(struct localvar_list *stop) +{ + while (localvar_stack != stop) + poplocalvars(0); } static int evalfun(struct funcnode *func, int argc, char **argv, int flags) { volatile struct shparam saveparam; - struct localvar *volatile savelocalvars; struct jmploc *volatile savehandler; struct jmploc jmploc; int e; saveparam = shellparam; - savelocalvars = localvars; savehandler = exception_handler; e = setjmp(jmploc.loc); if (e) { @@ -9113,7 +9241,6 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) } INT_OFF; exception_handler = &jmploc; - localvars = NULL; shellparam.malloced = 0; func->count++; funcnest++; @@ -9124,13 +9251,13 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) shellparam.optind = 1; shellparam.optoff = -1; #endif + pushlocalvars(); evaltree(func->n.narg.next, flags & EV_TESTED); + poplocalvars(0); funcdone: INT_OFF; funcnest--; freefunc(func); - poplocalvars(); - localvars = savelocalvars; freeparam(&shellparam); shellparam = saveparam; exception_handler = savehandler; @@ -9159,7 +9286,7 @@ mklocal(char *name) * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x */ - lvp = localvars; + lvp = localvar_stack->lv; while (lvp) { if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) { if (eq) @@ -9184,10 +9311,9 @@ mklocal(char *name) if (vp == NULL) { /* variable did not exist yet */ if (eq) - setvareq(name, VSTRFIXED); + vp = setvareq(name, VSTRFIXED); else - setvar(name, NULL, VSTRFIXED); - vp = *vpp; /* the new variable */ + vp = setvar(name, NULL, VSTRFIXED); lvp->flags = VUNSET; } else { lvp->text = vp->var_text; @@ -9204,8 +9330,8 @@ mklocal(char *name) } } lvp->vp = vp; - lvp->next = localvars; - localvars = lvp; + lvp->next = localvar_stack->lv; + localvar_stack->lv = lvp; ret: INT_ON; } @@ -9218,7 +9344,7 @@ localcmd(int argc UNUSED_PARAM, char **argv) { char *name; - if (!funcnest) + if (!localvar_stack) ash_msg_and_raise_error("not in a function"); argv = argptr; @@ -9243,7 +9369,14 @@ truecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) static int FAST_FUNC execcmd(int argc UNUSED_PARAM, char **argv) { - if (argv[1]) { + optionarg = NULL; + while (nextopt("a:") != '\0') + /* nextopt() sets optionarg to "-a ARGV0" */; + + argv = argptr; + if (argv[0]) { + char *prog; + iflag = 0; /* exit on error */ mflag = 0; optschanged(); @@ -9259,7 +9392,10 @@ execcmd(int argc UNUSED_PARAM, char **argv) /*setsignal(SIGTSTP); - unnecessary because of mflag=0 */ /*setsignal(SIGTTOU); - unnecessary because of mflag=0 */ - shellexec(argv + 1, pathval(), 0); + prog = argv[0]; + if (optionarg) + argv[0] = optionarg; + shellexec(prog, argv, pathval(), 0); /* NOTREACHED */ } return 0; @@ -9377,7 +9513,7 @@ static const struct builtincmd builtintab[] = { #if ENABLE_FEATURE_SH_MATH { BUILTIN_NOSPEC "let" , letcmd }, #endif - { BUILTIN_ASSIGN "local" , localcmd }, + { BUILTIN_SPEC_REG_ASSG "local" , localcmd }, #if ENABLE_ASH_PRINTF { BUILTIN_REGULAR "printf" , printfcmd }, #endif @@ -9466,6 +9602,7 @@ evalcommand(union node *cmd, int flags) static const struct builtincmd null_bltin = { "\0\0", bltincmd /* why three NULs? */ }; + struct localvar_list *localvar_stop; struct stackmark smark; union node *argp; struct arglist arglist; @@ -9487,6 +9624,7 @@ evalcommand(union node *cmd, int flags) /* First expand the arguments. */ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); setstackmark(&smark); + localvar_stop = pushlocalvars(); back_exitstatus = 0; cmdentry.cmdtype = CMDBUILTIN; @@ -9540,6 +9678,8 @@ evalcommand(union node *cmd, int flags) spp = varlist.lastp; expandarg(argp, &varlist, EXP_VARTILDE); + mklocal((*spp)->text); + /* * Modify the command lookup path, if a PATH= assignment * is present @@ -9551,18 +9691,36 @@ evalcommand(union node *cmd, int flags) /* Print the command if xflag is set. */ if (xflag) { - int n; - const char *p = " %s" + 1; + const char *pfx = ""; + + fdprintf(preverrout_fd, "%s", expandstr(ps4val())); - fdprintf(preverrout_fd, p, expandstr(ps4val())); sp = varlist.list; - for (n = 0; n < 2; n++) { - while (sp) { - fdprintf(preverrout_fd, p, sp->text); - sp = sp->next; - p = " %s"; - } - sp = arglist.list; + while (sp) { + char *varval = sp->text; + char *eq = strchrnul(varval, '='); + if (*eq) + eq++; + fdprintf(preverrout_fd, "%s%.*s%s", + pfx, + (int)(eq - varval), varval, + maybe_single_quote(eq) + ); + sp = sp->next; + pfx = " "; + } + + sp = arglist.list; + while (sp) { + fdprintf(preverrout_fd, "%s%s", + pfx, + /* always quote if matches reserved word: */ + findkwd(sp->text) + ? single_quote(sp->text) + : maybe_single_quote(sp->text) + ); + sp = sp->next; + pfx = " "; } safe_write(preverrout_fd, "\n", 1); } @@ -9647,6 +9805,7 @@ evalcommand(union node *cmd, int flags) if (!(flags & EV_EXIT) || may_have_traps) { /* No, forking off a child is necessary */ INT_OFF; + get_tty_state(); jp = makejob(/*cmd,*/ 1); if (forkshell(jp, cmd, FORK_FG) != 0) { /* parent */ @@ -9660,21 +9819,16 @@ evalcommand(union node *cmd, int flags) /* fall through to exec'ing external program */ } listsetvar(varlist.list, VEXPORT|VSTACK); - shellexec(argv, path, cmdentry.u.index); + shellexec(argv[0], argv, path, cmdentry.u.index); /* NOTREACHED */ } /* default */ case CMDBUILTIN: - cmdenviron = varlist.list; - if (cmdenviron) { - struct strlist *list = cmdenviron; - int i = VNOSET; - if (spclbltin > 0 || argc == 0) { - i = 0; - if (cmd_is_exec && argc > 1) - i = VEXPORT; - } - listsetvar(list, i); + if (spclbltin > 0 || argc == 0) { + poplocalvars(1); + if (cmd_is_exec && argc > 1) + listsetvar(varlist.list, VEXPORT); } + /* Tight loop with builtins only: * "while kill -0 $child; do true; done" * will never exit even if $child died, unless we do this @@ -9692,7 +9846,7 @@ evalcommand(union node *cmd, int flags) goto readstatus; case CMDFUNCTION: - listsetvar(varlist.list, 0); + poplocalvars(1); /* See above for the rationale */ dowait(DOWAIT_NONBLOCK, NULL); if (evalfun(cmdentry.u.func, argc, argv, flags)) @@ -9705,6 +9859,7 @@ evalcommand(union node *cmd, int flags) out: if (cmd->ncmd.redirect) popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); + unwindlocalvars(localvar_stop); if (lastarg) { /* dsl: I think this is intended to be used to support * '_' in 'vi' command mode during line editing... @@ -11428,9 +11583,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) smallint dblquote; smallint oldstyle; IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ -#if ENABLE_ASH_EXPAND_PRMT smallint pssyntax; /* we are expanding a prompt string */ -#endif int varnest; /* levels of variables expansion */ IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ @@ -11442,11 +11595,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) bqlist = NULL; quotef = 0; IF_FEATURE_SH_MATH(prevsyntax = 0;) -#if ENABLE_ASH_EXPAND_PRMT pssyntax = (syntax == PSSYNTAX); if (pssyntax) syntax = DQSYNTAX; -#endif dblquote = (syntax == DQSYNTAX); varnest = 0; IF_FEATURE_SH_MATH(arinest = 0;) @@ -11500,12 +11651,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } else if (c == '\n') { nlprompt(); } else { -#if ENABLE_ASH_EXPAND_PRMT if (c == '$' && pssyntax) { USTPUTC(CTLESC, out); USTPUTC('\\', out); } -#endif /* Backslash is retained if we are in "str" and next char isn't special */ if (dblquote && c != '\\' @@ -11790,7 +11939,7 @@ parsesub: { #if ENABLE_FEATURE_SH_MATH PARSEARITH(); #else - raise_error_syntax("you disabled math support for $((arith)) syntax"); + raise_error_syntax("support for $((arith)) is disabled"); #endif } else { pungetc(); @@ -12339,7 +12488,6 @@ parseheredoc(void) /* * called by editline -- any expansions to the prompt should be added here. */ -#if ENABLE_ASH_EXPAND_PRMT static const char * expandstr(const char *ps) { @@ -12365,7 +12513,12 @@ expandstr(const char *ps) expandarg(&n, NULL, EXP_QUOTED); return stackblock(); } -#endif + +static inline int +parser_eof(void) +{ + return tokpushback && lasttoken == TEOF; +} /* * Execute a command or commands contained in a string. @@ -12402,7 +12555,7 @@ evalstring(char *s, int flags) while ((n = parsecmd(0)) != NODE_EOF) { int i; - i = evaltree(n, flags); + i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT)); if (n) status = i; popstackmark(&smark); @@ -12553,11 +12706,12 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) char *fullname; char **argv; char *args_need_save; - struct strlist *sp; volatile struct shparam saveparam; - for (sp = cmdenviron; sp; sp = sp->next) - setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); +//??? +// struct strlist *sp; +// for (sp = cmdenviron; sp; sp = sp->next) +// setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); nextopt(nullstr); /* handle possible "--" */ argv = argptr; @@ -12573,7 +12727,7 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) fullname = find_dot_file(argv[0]); argv++; args_need_save = argv[0]; - if (args_need_save) { /* . FILE ARGS, ARGS exist */ + if (args_need_save) { /* ". FILE ARGS", and ARGS are not empty */ int argc; saveparam = shellparam; shellparam.malloced = 0; @@ -12857,9 +13011,14 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return 0; } + /* Why the second check? + * "trap NUM [sig2]..." is the same as "trap - NUM [sig2]..." + * In this case, NUM is signal no, not an action. + */ action = NULL; - if (ap[1]) + if (ap[1] && !is_number(ap[0])) action = *ap++; + exitcode = 0; while (*ap) { signo = get_signum(*ap); @@ -12968,7 +13127,7 @@ exportcmd(int argc UNUSED_PARAM, char **argv) } flag_off = ~flag_off; - /*if (opt_p_not_specified) - bash doesnt check this. Try "export -p NAME" */ + /*if (opt_p_not_specified) - bash doesn't check this. Try "export -p NAME" */ { aptr = argptr; name = *aptr; @@ -13021,7 +13180,6 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) char **ap; int i; int flag = 0; - int ret = 0; while ((i = nextopt("vf")) != 0) { flag = i; @@ -13029,15 +13187,13 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) for (ap = argptr; *ap; ap++) { if (flag != 'f') { - i = unsetvar(*ap); - ret |= i; - if (!(i & 2)) - continue; + unsetvar(*ap); + continue; } if (flag != 'v') unsetfunc(*ap); } - return ret & 1; + return 0; } static const unsigned char timescmd_str[] ALIGN1 = { @@ -13148,6 +13304,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* "read -s" needs to save/restore termios, can't allow ^C * to jump out of it. */ + again: INT_OFF; r = shell_builtin_read(setvar0, argptr, @@ -13160,6 +13317,12 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) ); INT_ON; + if ((uintptr_t)r == 1 && errno == EINTR) { + /* to get SIGCHLD: sleep 1 & read x; echo $x */ + if (pending_sig == 0) + goto again; + } + if ((uintptr_t)r > 1) ash_msg_and_raise_error(r); @@ -13367,7 +13530,7 @@ procargs(char **argv) #if DEBUG == 2 debug = 1; #endif - /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + /* POSIX 1003.2: first arg after "-c CMD" is $0, remainder $1... */ if (xminusc) { minusc = *xargv++; if (*xargv) @@ -13428,6 +13591,9 @@ reset(void) /* from redir.c: */ while (redirlist) popredir(/*drop:*/ 0, /*restore:*/ 0); + + /* from var.c: */ + unwindlocalvars(NULL); } #if PROFILE @@ -13538,7 +13704,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) // if (!sflag) g_parsefile->pf_fd = -1; // ^^ not necessary since now we special-case fd 0 // in is_hidden_fd() to not be considered "hidden fd" - evalstring(minusc, 0); + evalstring(minusc, sflag ? 0 : EV_EXIT); } if (sflag || minusc == NULL) { @@ -13548,9 +13714,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv) if (!hp) { hp = lookupvar("HOME"); if (hp) { + INT_OFF; hp = concat_path_file(hp, ".ash_history"); setvar0("HISTFILE", hp); free((char*)hp); + INT_ON; hp = lookupvar("HISTFILE"); } }