X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fash.c;h=c177ac038982cee244d5f2aa208ba236859467bd;hb=acf79f9913e4cf9b2889404af6758ec8a0d6b090;hp=e80425f5ee2d1fba468b5c344020f99905f8bd8f;hpb=f8cdc7a2bcd0a9d067f5ca7da8ce7bc9c98cf34e;p=oweals%2Fbusybox.git diff --git a/shell/ash.c b/shell/ash.c index e80425f5e..c177ac038 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -16,7 +16,7 @@ * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ //config:config ASH -//config: bool "ash (77 kb)" +//config: bool "ash (78 kb)" //config: default y //config: depends on !NOMMU //config: help @@ -51,6 +51,22 @@ //config: default y //config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: +//config:config ASH_BASH_SOURCE_CURDIR +//config: bool "'source' and '.' builtins search current directory after $PATH" +//config: default n # do not encourage non-standard behavior +//config: depends on ASH_BASH_COMPAT +//config: help +//config: This is not compliant with standards. Avoid if possible. +//config: +//config:config ASH_BASH_NOT_FOUND_HOOK +//config: bool "command_not_found_handle hook support" +//config: default y +//config: depends on ASH_BASH_COMPAT +//config: help +//config: Enable support for the 'command_not_found_handle' hook function, +//config: from GNU bash, which allows for alternative command not found +//config: handling. +//config: //config:config ASH_JOB_CONTROL //config: bool "Job control" //config: default y @@ -161,11 +177,15 @@ #define JOBS ENABLE_ASH_JOB_CONTROL -#include #include #include #include /* for setting $HOSTNAME */ #include "busybox.h" /* for applet_names */ +#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS +# include "embedded_scripts.h" +#else +# define NUM_SCRIPTS 0 +#endif /* So far, all bash compat is controlled by one config option */ /* Separate defines document which part of code implements what */ @@ -182,13 +202,30 @@ #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT -/* [[ EXPR ]] */ +/* BASH_TEST2: [[ EXPR ]] + * Status of [[ support: + * We replace && and || with -a and -o + * TODO: + * singleword+noglob expansion: + * v='a b'; [[ $v = 'a b' ]]; echo 0:$? + * [[ /bin/n* ]]; echo 0:$? + * -a/-o are not AND/OR ops! (they are just strings) + * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) + * = is glob match operator, not equality operator: STR = GLOB + * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") + * == same as = + * add =~ regex match operator: STR =~ REGEX + */ #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT #define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT +#define BASH_EPOCH_VARS ENABLE_ASH_BASH_COMPAT #define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT #define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT +#define BASH_READ_D ENABLE_ASH_BASH_COMPAT +#define IF_BASH_READ_D IF_ASH_BASH_COMPAT +#define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT #if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 /* Bionic at least up to version 24 has no glob() */ @@ -237,6 +274,12 @@ typedef long arith_t; # define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__ #endif +#ifndef F_DUPFD_CLOEXEC +# define F_DUPFD_CLOEXEC F_DUPFD +#endif +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif #ifndef PIPE_BUF # define PIPE_BUF 4096 /* amount of buffering in a pipe */ #endif @@ -245,6 +288,19 @@ typedef long arith_t; # error "Do not even bother, ash will not run on NOMMU machine" #endif +/* We use a trick to have more optimized code (fewer pointer reloads): + * ash.c: extern struct globals *const ash_ptr_to_globals; + * ash_ptr_hack.c: struct globals *ash_ptr_to_globals; + * This way, compiler in ash.c knows the pointer can not change. + * + * However, this may break on weird arches or toolchains. In this case, + * set "-DBB_GLOBAL_CONST=''" in CONFIG_EXTRA_CFLAGS to disable + * this optimization. + */ +#ifndef BB_GLOBAL_CONST +# define BB_GLOBAL_CONST const +#endif + /* ============ Hash table sizes. Configurable. */ @@ -259,10 +315,18 @@ static const char *const optletters_optnames[] = { "e" "errexit", "f" "noglob", "I" "ignoreeof", - "i" "interactive", +/* The below allowed this invocation: + * ash -c 'set -i; echo $-; sleep 5; echo $-' + * to be ^C-ed and get to interactive ash prompt. + * bash does not support such "set -i". + * In our code, this is denoted by empty long name: + */ + "i" "", "m" "monitor", "n" "noexec", - "s" "stdin", +/* Ditto: bash has no "set -s" */ + "s" "", + "c" "", "x" "xtrace", "v" "verbose", "C" "noclobber", @@ -278,6 +342,20 @@ static const char *const optletters_optnames[] = { ,"\0" "debug" #endif }; +//bash 4.4.23 also has these opts (with these defaults): +//braceexpand on +//emacs on +//errtrace off +//functrace off +//hashall on +//histexpand off +//history on +//interactive-comments on +//keyword off +//onecmd off +//physical off +//posix off +//privileged off #define optletters(n) optletters_optnames[n][0] #define optnames(n) (optletters_optnames[n] + 1) @@ -310,6 +388,8 @@ struct globals_misc { /* shell level: 0 for the main shell, 1 for its children, and so on */ int shlvl; #define rootshell (!shlvl) + int errlinno; + char *minusc; /* argument to -c option */ char *curdir; // = nullstr; /* current working directory */ @@ -339,21 +419,22 @@ struct globals_misc { #define mflag optlist[4] #define nflag optlist[5] #define sflag optlist[6] -#define xflag optlist[7] -#define vflag optlist[8] -#define Cflag optlist[9] -#define aflag optlist[10] -#define bflag optlist[11] -#define uflag optlist[12] -#define viflag optlist[13] +#define cflag optlist[7] +#define xflag optlist[8] +#define vflag optlist[9] +#define Cflag optlist[10] +#define aflag optlist[11] +#define bflag optlist[12] +#define uflag optlist[13] +#define viflag optlist[14] #if BASH_PIPEFAIL -# define pipefail optlist[14] +# define pipefail optlist[15] #else # define pipefail 0 #endif #if DEBUG -# define nolog optlist[14 + BASH_PIPEFAIL] -# define debug optlist[15 + BASH_PIPEFAIL] +# define nolog optlist[15 + BASH_PIPEFAIL] +# define debug optlist[16 + BASH_PIPEFAIL] #endif /* trap handler commands */ @@ -366,7 +447,7 @@ struct globals_misc { #define S_DFL 1 /* default signal handling (SIG_DFL) */ #define S_CATCH 2 /* signal is caught */ #define S_IGN 3 /* signal is ignored (SIG_IGN) */ -#define S_HARD_IGN 4 /* signal is ignored permanently */ +#define S_HARD_IGN 4 /* signal is ignored permanently (it was SIG_IGN on entry to shell) */ /* indicates specified signal received */ uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ @@ -380,13 +461,14 @@ struct globals_misc { #endif pid_t backgndpid; /* pid of last background process */ }; -extern struct globals_misc *const ash_ptr_to_globals_misc; +extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc; #define G_misc (*ash_ptr_to_globals_misc) #define exitstatus (G_misc.exitstatus ) #define back_exitstatus (G_misc.back_exitstatus ) #define job_warning (G_misc.job_warning) #define rootpid (G_misc.rootpid ) #define shlvl (G_misc.shlvl ) +#define errlinno (G_misc.errlinno ) #define minusc (G_misc.minusc ) #define curdir (G_misc.curdir ) #define physdir (G_misc.physdir ) @@ -407,7 +489,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; #define random_gen (G_misc.random_gen ) #define backgndpid (G_misc.backgndpid ) #define INIT_G_misc() do { \ - (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \ + (*(struct globals_misc**)not_const_pp(&ash_ptr_to_globals_misc)) = xzalloc(sizeof(G_misc)); \ barrier(); \ curdir = nullstr; \ physdir = nullstr; \ @@ -721,6 +803,7 @@ union node; struct ncmd { smallint type; /* Nxxxx */ + int linno; union node *assign; union node *args; union node *redirect; @@ -734,6 +817,7 @@ struct npipe { struct nredir { smallint type; + int linno; union node *n; union node *redirect; }; @@ -753,6 +837,7 @@ struct nif { struct nfor { smallint type; + int linno; union node *args; union node *body; char *var; @@ -760,6 +845,7 @@ struct nfor { struct ncase { smallint type; + int linno; union node *expr; union node *cases; }; @@ -771,6 +857,13 @@ struct nclist { union node *body; }; +struct ndefun { + smallint type; + int linno; + char *text; + union node *body; +}; + struct narg { smallint type; union node *next; @@ -822,6 +915,7 @@ union node { struct nfor nfor; struct ncase ncase; struct nclist nclist; + struct ndefun ndefun; struct narg narg; struct nfile nfile; struct ndup ndup; @@ -1227,6 +1321,10 @@ struct strpush { int unget; }; +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ struct parsefile { struct parsefile *prev; /* preceding file on stack */ int linno; /* current line */ @@ -1247,7 +1345,6 @@ struct parsefile { 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 */ @@ -1261,7 +1358,7 @@ ash_vmsg(const char *msg, va_list ap) if (strcmp(arg0, commandname)) fprintf(stderr, "%s: ", commandname); if (!iflag || g_parsefile->pf_fd > 0) - fprintf(stderr, "line %d: ", startlinno); + fprintf(stderr, "line %d: ", errlinno); } vfprintf(stderr, msg, ap); newline_and_flush(stderr); @@ -1306,21 +1403,15 @@ ash_msg_and_raise_error(const char *msg, ...) } /* - * Use '%m' to append error string on platforms that support it, '%s' and - * strerror() on those that don't. - * * 'fmt' must be a string literal. */ -#ifdef HAVE_PRINTF_PERCENTM -#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": %m", ##__VA_ARGS__) -#else -#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": %s", ##__VA_ARGS__, strerror(errno)) -#endif +#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": "STRERROR_FMT, ##__VA_ARGS__ STRERROR_ERRNO) static void raise_error_syntax(const char *) NORETURN; static void raise_error_syntax(const char *msg) { + errlinno = g_parsefile->linno; ash_msg_and_raise_error("syntax error: %s", msg); /* NOTREACHED */ } @@ -1443,7 +1534,7 @@ struct globals_memstack { size_t g_stacknleft; // = MINSIZE; struct stack_block stackbase; }; -extern struct globals_memstack *const ash_ptr_to_globals_memstack; +extern struct globals_memstack *BB_GLOBAL_CONST ash_ptr_to_globals_memstack; #define G_memstack (*ash_ptr_to_globals_memstack) #define g_stackp (G_memstack.g_stackp ) #define g_stacknxt (G_memstack.g_stacknxt ) @@ -1451,7 +1542,7 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack; #define g_stacknleft (G_memstack.g_stacknleft) #define stackbase (G_memstack.stackbase ) #define INIT_G_memstack() do { \ - (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \ + (*(struct globals_memstack**)not_const_pp(&ash_ptr_to_globals_memstack)) = xzalloc(sizeof(G_memstack)); \ barrier(); \ g_stackp = &stackbase; \ g_stacknxt = stackbase.space; \ @@ -1533,7 +1624,7 @@ sstrdup(const char *p) return memcpy(stalloc(len), p, len); } -static inline void +static ALWAYS_INLINE void grabstackblock(size_t len) { stalloc(len); @@ -1900,10 +1991,6 @@ nextopt(const char *optstring) /* ============ Shell variables */ -/* - * The parsefile structure pointed to by the global variable parsefile - * contains information about the current file being read. - */ struct shparam { int nparam; /* # of positional parameters (without $0) */ #if ENABLE_ASH_GETOPTS @@ -1990,6 +2077,10 @@ static void changepath(const char *) FAST_FUNC; #if ENABLE_ASH_RANDOM_SUPPORT static void change_random(const char *) FAST_FUNC; #endif +#if BASH_EPOCH_VARS +static void change_seconds(const char *) FAST_FUNC; +static void change_realtime(const char *) FAST_FUNC; +#endif static const struct { int flags; @@ -2012,9 +2103,14 @@ static const struct { #if ENABLE_ASH_GETOPTS { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset }, #endif + { VSTRFIXED|VTEXTFIXED , NULL /* inited to linenovar */, NULL }, #if ENABLE_ASH_RANDOM_SUPPORT { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random }, #endif +#if BASH_EPOCH_VARS + { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "EPOCHSECONDS", change_seconds }, + { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "EPOCHREALTIME", change_realtime }, +#endif #if ENABLE_LOCALE_SUPPORT { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL" , change_lc_all }, { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE" , change_lc_ctype }, @@ -2032,47 +2128,54 @@ struct globals_var { int preverrout_fd; /* stderr fd: usually 2, unless redirect moved it */ struct var *vartab[VTABSIZE]; struct var varinit[ARRAY_SIZE(varinit_data)]; + int lineno; + char linenovar[sizeof("LINENO=") + sizeof(int)*3]; }; -extern struct globals_var *const ash_ptr_to_globals_var; +extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var; #define G_var (*ash_ptr_to_globals_var) #define shellparam (G_var.shellparam ) //#define redirlist (G_var.redirlist ) #define preverrout_fd (G_var.preverrout_fd) #define vartab (G_var.vartab ) #define varinit (G_var.varinit ) +#define lineno (G_var.lineno ) +#define linenovar (G_var.linenovar ) +#define vifs varinit[0] +#if ENABLE_ASH_MAIL +# define vmail varinit[1] +# define vmpath varinit[2] +#endif +#define VAR_OFFSET1 (ENABLE_ASH_MAIL*2) +#define vpath varinit[VAR_OFFSET1 + 1] +#define vps1 varinit[VAR_OFFSET1 + 2] +#define vps2 varinit[VAR_OFFSET1 + 3] +#define vps4 varinit[VAR_OFFSET1 + 4] +#if ENABLE_ASH_GETOPTS +# define voptind varinit[VAR_OFFSET1 + 5] +#endif +#define VAR_OFFSET2 (VAR_OFFSET1 + ENABLE_ASH_GETOPTS) +#define vlineno varinit[VAR_OFFSET2 + 5] +#if ENABLE_ASH_RANDOM_SUPPORT +# define vrandom varinit[VAR_OFFSET2 + 6] +#endif +#define VAR_OFFSET3 (VAR_OFFSET2 + ENABLE_ASH_RANDOM_SUPPORT) +#if BASH_EPOCH_VARS +# define vepochs varinit[VAR_OFFSET3 + 6] +# define vepochr varinit[VAR_OFFSET3 + 7] +#endif #define INIT_G_var() do { \ unsigned i; \ - (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \ + (*(struct globals_var**)not_const_pp(&ash_ptr_to_globals_var)) = xzalloc(sizeof(G_var)); \ barrier(); \ for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \ varinit[i].flags = varinit_data[i].flags; \ varinit[i].var_text = varinit_data[i].var_text; \ varinit[i].var_func = varinit_data[i].var_func; \ } \ + strcpy(linenovar, "LINENO="); \ + vlineno.var_text = linenovar; \ } while (0) -#define vifs varinit[0] -#if ENABLE_ASH_MAIL -# define vmail (&vifs)[1] -# define vmpath (&vmail)[1] -# define vpath (&vmpath)[1] -#else -# define vpath (&vifs)[1] -#endif -#define vps1 (&vpath)[1] -#define vps2 (&vps1)[1] -#define vps4 (&vps2)[1] -#if ENABLE_ASH_GETOPTS -# define voptind (&vps4)[1] -# if ENABLE_ASH_RANDOM_SUPPORT -# define vrandom (&voptind)[1] -# endif -#else -# if ENABLE_ASH_RANDOM_SUPPORT -# define vrandom (&vps4)[1] -# endif -#endif - /* * The following macros access the values of the above variables. * They have to skip over the name. They return the null string @@ -2097,7 +2200,9 @@ extern struct globals_var *const ash_ptr_to_globals_var; static void FAST_FUNC getoptsreset(const char *value) { - shellparam.optind = number(value) ?: 1; + shellparam.optind = 1; + if (is_number(value)) + shellparam.optind = number(value) ?: 1; shellparam.optoff = -1; } #endif @@ -2195,7 +2300,7 @@ lookupvar(const char *name) v = *findvar(hashvar(name), name); if (v) { -#if ENABLE_ASH_RANDOM_SUPPORT +#if ENABLE_ASH_RANDOM_SUPPORT || BASH_EPOCH_VARS /* * Dynamic variables are implemented roughly the same way they are * in bash. Namely, they're "special" so long as they aren't unset. @@ -2205,8 +2310,12 @@ lookupvar(const char *name) if (v->flags & VDYNAMIC) v->var_func(NULL); #endif - if (!(v->flags & VUNSET)) + if (!(v->flags & VUNSET)) { + if (v == &vlineno && v->var_text == linenovar) { + fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno); + } return var_end(v->var_text); + } } return NULL; } @@ -2287,6 +2396,10 @@ setvareq(char *s, int flags) } flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); +#if ENABLE_ASH_RANDOM_SUPPORT || BASH_EPOCH_VARS + if (flags & VUNSET) + flags &= ~VDYNAMIC; +#endif } else { /* variable s is not found */ if (flags & VNOSET) @@ -2334,13 +2447,12 @@ setvar(const char *name, const char *val, int flags) } INT_OFF; - nameeq = ckmalloc(namelen + vallen + 2); + nameeq = ckzalloc(namelen + vallen + 2); p = mempcpy(nameeq, name, namelen); if (val) { *p++ = '='; - p = mempcpy(p, val, vallen); + memcpy(p, val, vallen); } - *p = '\0'; vp = setvareq(nameeq, flags | VNOSAVE); INT_ON; @@ -2383,8 +2495,11 @@ listsetvar(struct strlist *list_set_var, int flags) /* * Generate a list of variables satisfying the given conditions. */ +#if !ENABLE_FEATURE_SH_NOFORK +# define listvars(on, off, lp, end) listvars(on, off, end) +#endif static char ** -listvars(int on, int off, char ***end) +listvars(int on, int off, struct strlist *lp, char ***end) { struct var **vpp; struct var *vp; @@ -2397,12 +2512,40 @@ listvars(int on, int off, char ***end) do { for (vp = *vpp; vp; vp = vp->next) { if ((vp->flags & mask) == on) { +#if ENABLE_FEATURE_SH_NOFORK + /* If variable with the same name is both + * exported and temporarily set for a command: + * export ZVAR=5 + * ZVAR=6 printenv + * then "ZVAR=6" will be both in vartab and + * lp lists. Do not pass it twice to printenv. + */ + struct strlist *lp1 = lp; + while (lp1) { + if (strcmp(lp1->text, vp->var_text) == 0) + goto skip; + lp1 = lp1->next; + } +#endif if (ep == stackstrend()) ep = growstackstr(); *ep++ = (char*)vp->var_text; +#if ENABLE_FEATURE_SH_NOFORK + skip: ; +#endif } } } while (++vpp < vartab + VTABSIZE); + +#if ENABLE_FEATURE_SH_NOFORK + while (lp) { + if (ep == stackstrend()) + ep = growstackstr(); + *ep++ = lp->text; + lp = lp->next; + } +#endif + if (ep == stackstrend()) ep = growstackstr(); if (end) @@ -2744,7 +2887,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) goto docd; err: - ash_msg_and_raise_error("can't cd to %s", dest); + ash_msg_and_raise_perror("can't cd to %s", dest); /* NOTREACHED */ out: if (flags & CD_PRINT) @@ -3416,7 +3559,7 @@ struct procstat { struct job { struct procstat ps0; /* status of process */ - struct procstat *ps; /* status or processes when more than one */ + struct procstat *ps; /* status of processes when more than one */ #if JOBS int stopstatus; /* status of a stopped job */ #endif @@ -3566,6 +3709,12 @@ setsignal(int signo) cur_act = S_IGN; /* don't hard ignore these */ } } + if (act.sa_handler == SIG_DFL && new_act == S_DFL) { + /* installing SIG_DFL over SIG_DFL is a no-op */ + /* saves one sigaction call in each "sh -c SCRIPT" invocation */ + *t = S_DFL; + return; + } } if (cur_act == S_HARD_IGN || cur_act == new_act) return; @@ -3879,12 +4028,13 @@ setjobctl(int on) goto out; } /* fd is a tty at this point */ - fd = fcntl(fd, F_DUPFD, 10); + fd = fcntl(fd, F_DUPFD_CLOEXEC, 10); 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 */ - close_on_exec_on(fd); + if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */ + close_on_exec_on(fd); while (1) { /* while we are in the background */ pgrp = tcgetpgrp(fd); if (pgrp < 0) { @@ -4104,7 +4254,7 @@ wait_block_or_sig(int *status) /* Children exist, but none are ready. Sleep until interesting signal */ #if 1 sigfillset(&mask); - sigprocmask(SIG_SETMASK, &mask, &mask); + sigprocmask2(SIG_SETMASK, &mask); /* mask is updated */ while (!got_sigchld && !pending_sig) sigsuspend(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); @@ -4122,6 +4272,9 @@ wait_block_or_sig(int *status) #define DOWAIT_NONBLOCK 0 #define DOWAIT_BLOCK 1 #define DOWAIT_BLOCK_OR_SIG 2 +#if BASH_WAIT_N +# define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */ +#endif static int dowait(int block, struct job *job) @@ -4129,7 +4282,11 @@ dowait(int block, struct job *job) int pid; int status; struct job *jp; - struct job *thisjob = NULL; + struct job *thisjob; +#if BASH_WAIT_N + bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS); + block = (block & ~DOWAIT_JOBSTATUS); +#endif TRACE(("dowait(0x%x) called\n", block)); @@ -4166,10 +4323,10 @@ dowait(int block, struct job *job) } TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", pid, status, errno, strerror(errno))); + thisjob = NULL; if (pid <= 0) goto out; - thisjob = NULL; for (jp = curjob; jp; jp = jp->prev_job) { int jobstate; struct procstat *ps; @@ -4228,6 +4385,13 @@ dowait(int block, struct job *job) out: INT_ON; +#if BASH_WAIT_N + if (want_jobexitstatus) { + pid = -1; + if (thisjob && thisjob->state == JOBDONE) + pid = thisjob->ps[thisjob->nprocs - 1].ps_status; + } +#endif if (thisjob && thisjob == job) { char s[48 + 1]; int len; @@ -4410,15 +4574,24 @@ waitcmd(int argc UNUSED_PARAM, char **argv) struct job *job; int retval; struct job *jp; - +#if BASH_WAIT_N + int status; + char one = nextopt("n"); +#else nextopt(nullstr); +#endif retval = 0; argv = argptr; - if (!*argv) { - /* wait for all jobs */ + if (!argv[0]) { + /* wait for all jobs / one job if -n */ for (;;) { jp = curjob; +#if BASH_WAIT_N + if (one && !jp) + /* exitcode of "wait -n" with nothing to wait for is 127, not 0 */ + retval = 127; +#endif while (1) { if (!jp) /* no running procs */ goto ret; @@ -4434,13 +4607,31 @@ waitcmd(int argc UNUSED_PARAM, char **argv) * with an exit status greater than 128, immediately after which * the trap is executed." */ +#if BASH_WAIT_N + status = dowait(DOWAIT_BLOCK_OR_SIG | DOWAIT_JOBSTATUS, NULL); +#else dowait(DOWAIT_BLOCK_OR_SIG, NULL); +#endif /* if child sends us a signal *and immediately exits*, * dowait() returns pid > 0. Check this case, * not "if (dowait() < 0)"! */ if (pending_sig) goto sigout; +#if BASH_WAIT_N + if (one) { + /* wait -n waits for one _job_, not one _process_. + * date; sleep 3 & sleep 2 | sleep 1 & wait -n; date + * should wait for 2 seconds. Not 1 or 3. + */ + if (status != -1 && !WIFSTOPPED(status)) { + retval = WEXITSTATUS(status); + if (WIFSIGNALED(status)) + retval = WTERMSIG(status) + 128; + goto ret; + } + } +#endif } } @@ -4773,7 +4964,7 @@ cmdtxt(union node *n) p = "; done"; goto dodo; case NDEFUN: - cmdputs(n->narg.text); + cmdputs(n->ndefun.text); p = "() { ... }"; goto dotail2; case NCMD: @@ -4984,7 +5175,7 @@ forkchild(struct job *jp, union node *n, int mode) if (jp->nprocs == 0) { close(0); if (open(bb_dev_null, O_RDONLY) != 0) - ash_msg_and_raise_error("can't open '%s'", bb_dev_null); + ash_msg_and_raise_perror("can't open '%s'", bb_dev_null); } } if (oldlvl == 0) { @@ -5073,7 +5264,7 @@ forkshell(struct job *jp, union node *n, int mode) TRACE(("Fork failed, errno=%d", errno)); if (jp) freejob(jp); - ash_msg_and_raise_error("can't fork"); + ash_msg_and_raise_perror("can't fork"); } if (pid == 0) { CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */ @@ -5216,7 +5407,7 @@ openhere(union node *redir) size_t len = 0; if (pipe(pip) < 0) - ash_msg_and_raise_error("pipe call failed"); + ash_msg_and_raise_perror("can't create pipe"); if (redir->type == NHERE) { len = strlen(redir->nhere.doc->narg.text); if (len <= PIPE_BUF) { @@ -5294,7 +5485,7 @@ openredirect(union node *redir) f = open(fname, O_WRONLY, 0666); if (f < 0) goto ecreate; - if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) { + if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) { close(f); errno = EEXIST; goto ecreate; @@ -5334,13 +5525,14 @@ savefd(int from) int newfd; int err; - newfd = fcntl(from, F_DUPFD, 10); + newfd = fcntl(from, F_DUPFD_CLOEXEC, 10); err = newfd < 0 ? errno : 0; if (err != EBADF) { if (err) ash_msg_and_raise_perror("%d", from); close(from); - fcntl(newfd, F_SETFD, FD_CLOEXEC); + if (F_DUPFD_CLOEXEC == F_DUPFD) + close_on_exec_on(newfd); } return newfd; @@ -5358,12 +5550,15 @@ dup2_or_raise(int from, int to) return newfd; } static int -fcntl_F_DUPFD(int fd, int avoid_fd) +dup_CLOEXEC(int fd, int avoid_fd) { int newfd; repeat: - newfd = fcntl(fd, F_DUPFD, avoid_fd + 1); - if (newfd < 0) { + newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1); + if (newfd >= 0) { + if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */ + close_on_exec_on(newfd); + } else { /* newfd < 0 */ if (errno == EBUSY) goto repeat; if (errno == EINTR) @@ -5376,7 +5571,7 @@ xdup_CLOEXEC_and_close(int fd, int avoid_fd) { int newfd; repeat: - newfd = fcntl(fd, F_DUPFD, avoid_fd + 1); + newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1); if (newfd < 0) { if (errno == EBUSY) goto repeat; @@ -5387,7 +5582,8 @@ xdup_CLOEXEC_and_close(int fd, int avoid_fd) return fd; ash_msg_and_raise_perror("%d", newfd); } - fcntl(newfd, F_SETFD, FD_CLOEXEC); + if (F_DUPFD_CLOEXEC == F_DUPFD) + close_on_exec_on(newfd); close(fd); return newfd; } @@ -5479,7 +5675,7 @@ save_fd_on_redirect(int fd, int avoid_fd, struct redirtab *sq) for (i = 0; sq->two_fd[i].orig_fd != EMPTY; i++) { /* If we collide with an already moved fd... */ if (fd == sq->two_fd[i].moved_to) { - new_fd = fcntl_F_DUPFD(fd, avoid_fd); + new_fd = dup_CLOEXEC(fd, avoid_fd); sq->two_fd[i].moved_to = new_fd; TRACE(("redirect_fd %d: already busy, moving to %d\n", fd, new_fd)); if (new_fd < 0) /* what? */ @@ -5494,7 +5690,7 @@ save_fd_on_redirect(int fd, int avoid_fd, struct redirtab *sq) } /* If this fd is open, we move and remember it; if it's closed, new_fd = CLOSED (-1) */ - new_fd = fcntl_F_DUPFD(fd, avoid_fd); + new_fd = dup_CLOEXEC(fd, avoid_fd); TRACE(("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, new_fd)); if (new_fd < 0) { if (errno != EBADF) @@ -5746,6 +5942,26 @@ ash_arith(const char *s) return result; } #endif +#if BASH_SUBSTR +# if ENABLE_FEATURE_SH_MATH +static int substr_atoi(const char *s) +{ + arith_t t = ash_arith(s); + if (sizeof(t) > sizeof(int)) { + /* clamp very large or very large negative nums for ${v:N:M}: + * else "${v:0:0x100000001}" would work as "${v:0:1}" + */ + if (t > INT_MAX) + t = INT_MAX; + if (t < INT_MIN) + t = INT_MIN; + } + return t; +} +# else +# define substr_atoi(s) number(s) +# endif +#endif /* * expandarg flags @@ -5766,9 +5982,8 @@ ash_arith(const char *s) * performs globbing, and thus diverges from what we do). */ #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ -#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */ -#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ -#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ +#define EXP_WORD 0x40 /* expand word in parameter expansion */ #define EXP_QUOTED 0x100 /* expand word in double quotes */ /* * rmescape() flags @@ -5777,10 +5992,9 @@ ash_arith(const char *s) #define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ #define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ #define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ -#define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */ /* Add CTLESC when necessary. */ -#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) +#define QUOTES_ESC (EXP_FULL | EXP_CASE) /* Do not skip NUL characters. */ #define QUOTES_KEEPNUL EXP_TILDE @@ -5855,7 +6069,10 @@ ifsbreakup(char *string, struct arglist *arglist) realifs = ifsset() ? ifsval() : defifs; ifsp = &ifsfirst; do { + int afternul; + p = string + ifsp->begoff; + afternul = nulonly; nulonly = ifsp->nulonly; ifs = nulonly ? nullstr : realifs; ifsspc = 0; @@ -5867,7 +6084,7 @@ ifsbreakup(char *string, struct arglist *arglist) p++; continue; } - if (!nulonly) + if (!(afternul || nulonly)) ifsspc = (strchr(defifs, *p) != NULL); /* Ignore IFS whitespace at start */ if (q == start && ifsspc) { @@ -5958,19 +6175,21 @@ esclen(const char *start, const char *p) /* * Remove any CTLESC characters from a string. */ +#if !BASH_PATTERN_SUBST +#define rmescapes(str, flag, slash_position) \ + rmescapes(str, flag) +#endif static char * -rmescapes(char *str, int flag) +rmescapes(char *str, int flag, int *slash_position) { static const char qchars[] ALIGN1 = { IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; char *p, *q, *r; - unsigned inquotes; unsigned protect_against_glob; unsigned globbing; - IF_BASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;) - p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash)); + p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash_position)); if (!p) return str; @@ -5997,18 +6216,21 @@ rmescapes(char *str, int flag) } } - inquotes = 0; globbing = flag & RMESCAPE_GLOB; protect_against_glob = globbing; while (*p) { if ((unsigned char)*p == CTLQUOTEMARK) { -// Note: both inquotes and protect_against_glob only affect whether +// Note: protect_against_glob only affect whether // CTLESC, gets converted to or to \ - inquotes = ~inquotes; p++; protect_against_glob = globbing; continue; } + if (*p == '\\') { + /* naked back slash */ + protect_against_glob = 0; + goto copy; + } if ((unsigned char)*p == CTLESC) { p++; #if DEBUG @@ -6034,26 +6256,23 @@ rmescapes(char *str, int flag) 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 */ + || *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 */ + || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */ ) { *q++ = '\\'; } } - } else if (*p == '\\' && !inquotes) { - /* naked back slash */ - protect_against_glob = 0; - goto copy; } #if BASH_PATTERN_SUBST - else if (*p == '/' && slash) { - /* stop handling globbing and mark location of slash */ - globbing = slash = 0; - *p = CTLESC; + else if (slash_position && p == str + *slash_position) { + /* stop handling globbing */ + globbing = 0; + *slash_position = q - r; + slash_position = NULL; } #endif protect_against_glob = globbing; @@ -6077,7 +6296,7 @@ rmescapes(char *str, int flag) static char * preglob(const char *pattern, int flag) { - return rmescapes((char *)pattern, flag | RMESCAPE_GLOB); + return rmescapes((char *)pattern, flag | RMESCAPE_GLOB, NULL); } /* @@ -6099,9 +6318,7 @@ memtodest(const char *p, size_t len, int syntax, int quotes) if (quotes & QUOTES_ESC) { int n = SIT(c, syntax); if (n == CCTL - || (((quotes & EXP_FULL) || syntax != BASESYNTAX) - && n == CBACK - ) + || (syntax != BASESYNTAX && n == CBACK) ) { USTPUTC(CTLESC, q); } @@ -6278,7 +6495,7 @@ evalbackcmd(union node *n, struct backcmd *result) } if (pipe(pip) < 0) - ash_msg_and_raise_error("pipe call failed"); + ash_msg_and_raise_perror("can't create pipe"); jp = makejob(/*n,*/ 1); if (forkshell(jp, n, FORK_NOJOB) == 0) { /* child */ @@ -6420,7 +6637,7 @@ expari(int flag) expdest = p; if (flag & QUOTES_ESC) - rmescapes(p + 1, 0); + rmescapes(p + 1, 0, NULL); len = cvtnum(ash_arith(p + 1)); @@ -6528,12 +6745,12 @@ argstr(char *p, int flags) case CTLENDVAR: /* ??? */ goto breakloop; case CTLQUOTEMARK: - inquotes ^= EXP_QUOTED; /* "$@" syntax adherence hack */ - if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { - p = evalvar(p + 1, flags | inquotes) + 1; + if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { + p = evalvar(p + 1, flags | EXP_QUOTED) + 1; goto start; } + inquotes ^= EXP_QUOTED; addquote: if (flags & QUOTES_ESC) { p--; @@ -6544,16 +6761,6 @@ argstr(char *p, int flags) case CTLESC: startloc++; length++; - - /* - * Quoted parameter expansion pattern: remove quote - * unless inside inner quotes or we have a literal - * backslash. - */ - if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) == - EXP_QPAT && *p != '\\') - break; - goto addquote; case CTLVAR: TRACE(("argstr: evalvar('%s')\n", p)); @@ -6708,20 +6915,75 @@ subevalvar(char *p, char *varname, int strloc, int subtype, char *rmesc, *rmescend; char *str; int amount, resetloc; + int argstr_flags; IF_BASH_PATTERN_SUBST(int workloc;) - IF_BASH_PATTERN_SUBST(char *repl = NULL;) + IF_BASH_PATTERN_SUBST(int slash_pos;) + IF_BASH_PATTERN_SUBST(char *repl;) int zero; char *(*scan)(char*, char*, char*, char*, int, int); //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)", // 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) - ); +#if BASH_PATTERN_SUBST + /* For "${v/pattern/repl}", we must find the delimiter _before_ + * argstr() call expands possible variable references in pattern: + * think about "v=a; a=a/; echo ${v/$a/r}" case. + */ + repl = NULL; + if (subtype == VSREPLACE || subtype == VSREPLACEALL) { + /* Find '/' and replace with NUL */ + repl = p; + /* The pattern can't be empty. + * IOW: if the first char after "${v//" is a slash, + * it does not terminate the pattern - it's the first char of the pattern: + * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/") + * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r") + */ + if (*repl == '/') + repl++; + for (;;) { + if (*repl == '\0') { + repl = NULL; + break; + } + if (*repl == '/') { + *repl = '\0'; + break; + } + /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */ + if ((unsigned char)*repl == CTLESC && repl[1]) + repl++; + repl++; + } + } +#endif + argstr_flags = EXP_TILDE; + if (subtype != VSASSIGN + && subtype != VSQUESTION +#if BASH_SUBSTR + && subtype != VSSUBSTR +#endif + ) { + /* EXP_CASE keeps CTLESC's */ + argstr_flags = EXP_TILDE | EXP_CASE; + } + argstr(p, argstr_flags); + //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc); +#if BASH_PATTERN_SUBST + slash_pos = -1; + if (repl) { + slash_pos = expdest - ((char *)stackblock() + strloc); + STPUTC('/', expdest); + //bb_error_msg("repl+1:'%s'", repl + 1); + argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */ + *repl = '/'; + } +#endif STPUTC('\0', expdest); argbackq = saveargbackq; startp = (char *)stackblock() + startloc; + //bb_error_msg("str1:'%s'", (char *)stackblock() + strloc); switch (subtype) { case VSASSIGN: @@ -6741,13 +7003,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype, loc = str = stackblock() + strloc; -# if !ENABLE_FEATURE_SH_MATH -# define ash_arith number -# endif /* Read POS in ${var:POS:LEN} */ colon = strchr(loc, ':'); if (colon) *colon = '\0'; - pos = ash_arith(loc); + pos = substr_atoi(loc); if (colon) *colon = ':'; /* Read LEN in ${var:POS:LEN} */ @@ -6755,7 +7014,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype, /* *loc != '\0', guaranteed by parser */ if (quotes) { char *ptr; - /* Adjust the length by the number of escapes */ for (ptr = startp; ptr < (str - 1); ptr++) { if ((unsigned char)*ptr == CTLESC) { @@ -6767,19 +7025,15 @@ subevalvar(char *p, char *varname, int strloc, int subtype, orig_len = len; if (*loc++ == ':') { /* ${var::LEN} */ - len = ash_arith(loc); + len = substr_atoi(loc); } else { /* Skip POS in ${var:POS:LEN} */ len = orig_len; - while (*loc && *loc != ':') { + while (*loc && *loc != ':') loc++; - } - if (*loc++ == ':') { - len = ash_arith(loc); - } + if (*loc++ == ':') + len = substr_atoi(loc); } -# undef ash_arith - if (pos < 0) { /* ${VAR:$((-n)):l} starts n chars from the end */ pos = orig_len + pos; @@ -6819,6 +7073,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype, resetloc = expdest - (char *)stackblock(); #if BASH_PATTERN_SUBST + repl = NULL; + /* We'll comeback here if we grow the stack while handling * a VSREPLACE or VSREPLACEALL, since our pointers into the * stack will need rebasing, and we'll need to remove our work @@ -6833,8 +7089,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype, rmesc = startp; rmescend = (char *)stackblock() + strloc; + //bb_error_msg("str7:'%s'", rmescend); if (quotes) { - rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); +//TODO: how to handle slash_pos here if string changes (shortens?) + rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW, NULL); if (rmesc != startp) { rmescend = expdest; startp = (char *)stackblock() + startloc; @@ -6847,12 +7105,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype, * The result is a_\_z_c (not a\_\_z_c)! * * The search pattern and replace string treat backslashes differently! - * RMESCAPE_SLASH causes preglob to work differently on the pattern + * "&slash_pos" causes rmescapes() to work differently on the pattern * and string. It's only used on the first call. */ - preglob(str, IF_BASH_PATTERN_SUBST( - (subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ? - RMESCAPE_SLASH : ) 0); + //bb_error_msg("str8:'%s' slash_pos:%d", str, slash_pos); + rmescapes(str, RMESCAPE_GLOB, + repl ? NULL : (slash_pos < 0 ? NULL : &slash_pos) + ); #if BASH_PATTERN_SUBST workloc = expdest - (char *)stackblock(); @@ -6861,11 +7120,12 @@ subevalvar(char *p, char *varname, int strloc, int subtype, char *idx, *end; if (!repl) { - repl = strchr(str, CTLESC); - if (repl) + //bb_error_msg("str9:'%s' slash_pos:%d", str, slash_pos); + repl = nullstr; + if (slash_pos >= 0) { + repl = str + slash_pos; *repl++ = '\0'; - else - repl = nullstr; + } } //bb_error_msg("str:'%s' repl:'%s'", str, repl); @@ -6995,14 +7255,13 @@ 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, int *quotedp) +varvalue(char *name, int varflags, int flags, int quoted) { const char *p; int num; int i; ssize_t len = 0; int sep; - int quoted = *quotedp; int subtype = varflags & VSTYPE; int discard = subtype == VSPLUS || subtype == VSLENGTH; int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; @@ -7031,7 +7290,7 @@ varvalue(char *name, int varflags, int flags, int *quotedp) case '-': expdest = makestrspace(NOPTS, expdest); for (i = NOPTS - 1; i >= 0; i--) { - if (optlist[i]) { + if (optlist[i] && optletters(i)) { USTPUTC(optletters(i), expdest); len++; } @@ -7050,13 +7309,27 @@ varvalue(char *name, int varflags, int flags, int *quotedp) case '*': { char **ap; char sepc; + char c; - if (quoted) - sep = 0; - sep |= ifsset() ? ifsval()[0] : ' '; + /* We will set c to 0 or ~0 depending on whether + * we're doing field splitting. We won't do field + * splitting if either we're quoted or sep is zero. + * + * Instead of testing (quoted || !sep) the following + * trick optimises away any branches by using the + * fact that EXP_QUOTED (which is the only bit that + * can be set in quoted) is the same as EXP_FULL << + * CHAR_BIT (which is the only bit that can be set + * in sep). + */ +#if EXP_QUOTED >> CHAR_BIT != EXP_FULL +#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT +#endif + c = !((quoted | ~sep) & EXP_QUOTED) - 1; + sep &= ~quoted; + sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' '; param: sepc = sep; - *quotedp = !sepc; ap = shellparam.p; if (!ap) return -1; @@ -7121,7 +7394,6 @@ evalvar(char *p, int flag) char varflags; char subtype; int quoted; - char easy; char *var; int patloc; int startloc; @@ -7135,12 +7407,11 @@ evalvar(char *p, int flag) quoted = flag & EXP_QUOTED; var = p; - easy = (!quoted || (*var == '@' && shellparam.nparam)); startloc = expdest - (char *)stackblock(); p = strchr(p, '=') + 1; //TODO: use var_end(p)? again: - varlen = varvalue(var, varflags, flag, "ed); + varlen = varvalue(var, varflags, flag, quoted); if (varflags & VSNUL) varlen--; @@ -7186,8 +7457,11 @@ evalvar(char *p, int flag) if (subtype == VSNORMAL) { record: - if (!easy) - goto end; + if (quoted) { + quoted = *var == '@' && shellparam.nparam; + if (!quoted) + goto end; + } recordregion(startloc, expdest - (char *)stackblock(), quoted); goto end; } @@ -7278,13 +7552,13 @@ hasmeta(const char *p) p = strpbrk(p, chars); if (!p) break; - switch ((unsigned char) *p) { + switch ((unsigned char)*p) { case CTLQUOTEMARK: for (;;) { p++; - if (*p == CTLQUOTEMARK) + if ((unsigned char)*p == CTLQUOTEMARK) break; - if (*p == CTLESC) + if ((unsigned char)*p == CTLESC) p++; if (*p == '\0') /* huh? */ return 0; @@ -7385,7 +7659,7 @@ expandmeta(struct strlist *str /*, int flag*/) INT_ON; nometa: *exparg.lastp = str; - rmescapes(str->text, 0); + rmescapes(str->text, 0, NULL); exparg.lastp = &str->next; break; default: /* GLOB_NOSPACE */ @@ -7403,9 +7677,16 @@ expandmeta(struct strlist *str /*, int flag*/) /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ +typedef struct exp_t { + char *dir; + unsigned dir_max; +} exp_t; static void -expmeta(char *expdir, char *enddir, char *name) +expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) { +#define expdir exp->dir +#define expdir_max exp->dir_max + char *enddir = expdir + expdir_len; char *p; const char *cp; char *start; @@ -7438,7 +7719,7 @@ expmeta(char *expdir, char *enddir, char *name) } } } else { - if (*p == '\\') + if (*p == '\\' && p[1]) esc++; if (p[esc] == '/') { if (metaflag) @@ -7448,15 +7729,15 @@ expmeta(char *expdir, char *enddir, char *name) } } if (metaflag == 0) { /* we've reached the end of the file name */ - if (enddir != expdir) - metaflag++; + if (!expdir_len) + return; p = name; do { - if (*p == '\\') + if (*p == '\\' && p[1]) p++; *enddir++ = *p; } while (*p++); - if (metaflag == 0 || lstat(expdir, &statb) >= 0) + if (lstat(expdir, &statb) == 0) addfname(expdir); return; } @@ -7464,24 +7745,19 @@ expmeta(char *expdir, char *enddir, char *name) if (name < start) { p = name; do { - if (*p == '\\') + if (*p == '\\' && p[1]) p++; *enddir++ = *p++; } while (p < start); } - if (enddir == expdir) { + *enddir = '\0'; + cp = expdir; + expdir_len = enddir - cp; + if (!expdir_len) cp = "."; - } else if (enddir == expdir + 1 && *expdir == '/') { - cp = "/"; - } else { - cp = expdir; - enddir[-1] = '\0'; - } dirp = opendir(cp); if (dirp == NULL) return; - if (enddir != expdir) - enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { @@ -7489,6 +7765,7 @@ expmeta(char *expdir, char *enddir, char *name) *endname = '\0'; endname += esc + 1; } + name_len -= endname - name; matchdot = 0; p = start; if (*p == '\\') @@ -7503,16 +7780,30 @@ expmeta(char *expdir, char *enddir, char *name) strcpy(enddir, dp->d_name); addfname(expdir); } else { - for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) - continue; - p[-1] = '/'; - expmeta(expdir, p, endname); + unsigned offset; + unsigned len; + + p = stpcpy(enddir, dp->d_name); + *p = '/'; + + offset = p - expdir + 1; + len = offset + name_len + NAME_MAX; + if (len > expdir_max) { + len += PATH_MAX; + expdir = ckrealloc(expdir, len); + expdir_max = len; + } + + expmeta(exp, endname, name_len, offset); + enddir = expdir + expdir_len; } } } closedir(dirp); if (!atend) endname[-esc - 1] = esc ? '\\' : '/'; +#undef expdir +#undef expdir_max } static struct strlist * @@ -7585,10 +7876,11 @@ expandmeta(struct strlist *str /*, int flag*/) /* TODO - EXP_REDIR */ while (str) { - char *expdir; + exp_t exp; struct strlist **savelastp; struct strlist *sp; char *p; + unsigned len; if (fflag) goto nometa; @@ -7598,13 +7890,12 @@ expandmeta(struct strlist *str /*, int flag*/) INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); - { - int i = strlen(str->text); -//BUGGY estimation of how long expanded name can be - expdir = ckmalloc(i < 2048 ? 2048 : i+1); - } - expmeta(expdir, expdir, p); - free(expdir); + len = strlen(p); + exp.dir_max = len + PATH_MAX; + exp.dir = ckmalloc(exp.dir_max); + + expmeta(&exp, p, len, 0); + free(exp.dir); if (p != str->text) free(p); INT_ON; @@ -7614,7 +7905,7 @@ expandmeta(struct strlist *str /*, int flag*/) */ nometa: *exparg.lastp = str; - rmescapes(str->text, 0); + rmescapes(str->text, 0, NULL); exparg.lastp = &str->next; } else { *exparg.lastp = NULL; @@ -7694,8 +7985,9 @@ static int patmatch(char *pattern, const char *string) { char *p = preglob(pattern, 0); - //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string); - return pmatch(p, string); + int r = pmatch(p, string); + //bb_error_msg("!fnmatch(pattern:'%s',str:'%s',0):%d", p, string, r); + return r; } /* @@ -7797,7 +8089,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c while (*envp) putenv(*envp++); popredir(/*drop:*/ 1); - run_applet_no_and_exit(applet_no, cmd, argv); + run_noexec_applet_and_exit(applet_no, cmd, argv); } /* re-exec ourselves with the new arguments */ execve(bb_busybox_exec_path, argv, envp); @@ -7814,6 +8106,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c #else execve(cmd, argv, envp); #endif + if (cmd != bb_busybox_exec_path && errno == ENOEXEC) { /* Run "cmd" as a shell script: * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html @@ -7856,7 +8149,7 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) int exerrno; int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */ - envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL); + envp = listvars(VEXPORT, VUNSET, /*strlist:*/ NULL, /*end:*/ NULL); if (strchr(prog, '/') != NULL #if ENABLE_FEATURE_SH_STANDALONE || (applet_no = find_applet_by_name(prog)) >= 0 @@ -7886,15 +8179,15 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) /* Map to POSIX errors */ switch (e) { - case EACCES: + default: exerrno = 126; break; + case ELOOP: + case ENAMETOOLONG: case ENOENT: + case ENOTDIR: exerrno = 127; break; - default: - exerrno = 2; - break; } exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", @@ -8322,7 +8615,8 @@ describe_command(char *command, const char *path, int describe_command_verbose) case CMDFUNCTION: if (describe_command_verbose) { - out1str(" is a shell function"); + /*out1str(" is a shell function");*/ + out1str(" is a function"); /* bash says this */ } else { out1str(command); } @@ -8540,6 +8834,9 @@ calcsize(int funcblocksize, union node *n) funcblocksize = calcsize(funcblocksize, n->nclist.next); break; case NDEFUN: + funcblocksize = calcsize(funcblocksize, n->ndefun.body); + funcblocksize += SHELL_ALIGN(strlen(n->ndefun.text) + 1); + break; case NARG: funcblocksize = sizenodelist(funcblocksize, n->narg.backquote); funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */ @@ -8615,6 +8912,7 @@ copynode(union node *n) new->ncmd.redirect = copynode(n->ncmd.redirect); new->ncmd.args = copynode(n->ncmd.args); new->ncmd.assign = copynode(n->ncmd.assign); + new->ncmd.linno = n->ncmd.linno; break; case NPIPE: new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); @@ -8625,6 +8923,7 @@ copynode(union node *n) case NSUBSHELL: new->nredir.redirect = copynode(n->nredir.redirect); new->nredir.n = copynode(n->nredir.n); + new->nredir.linno = n->nredir.linno; break; case NAND: case NOR: @@ -8643,10 +8942,12 @@ copynode(union node *n) new->nfor.var = nodeckstrdup(n->nfor.var); new->nfor.body = copynode(n->nfor.body); new->nfor.args = copynode(n->nfor.args); + new->nfor.linno = n->nfor.linno; break; case NCASE: new->ncase.cases = copynode(n->ncase.cases); new->ncase.expr = copynode(n->ncase.expr); + new->ncase.linno = n->ncase.linno; break; case NCLIST: new->nclist.body = copynode(n->nclist.body); @@ -8654,6 +8955,10 @@ copynode(union node *n) new->nclist.next = copynode(n->nclist.next); break; case NDEFUN: + new->ndefun.body = copynode(n->ndefun.body); + new->ndefun.text = nodeckstrdup(n->ndefun.text); + new->ndefun.linno = n->ndefun.linno; + break; case NARG: new->narg.backquote = copynodelist(n->narg.backquote); new->narg.text = nodeckstrdup(n->narg.text); @@ -8722,7 +9027,7 @@ defun(union node *func) INT_OFF; entry.cmdtype = CMDFUNCTION; entry.u.func = copyfunc(func); - addcmdentry(func->narg.text, &entry); + addcmdentry(func->ndefun.text, &entry); INT_ON; } @@ -8732,8 +9037,8 @@ defun(union node *func) #define SKIPFUNC (1 << 2) static smallint evalskip; /* set to SKIPxxx if we are skipping commands */ static int skipcount; /* number of levels to skip */ -static int funcnest; /* depth of function calls */ static int loopnest; /* current loop nesting level */ +static int funcline; /* starting line number of current function, or 0 if not in a function */ /* Forward decl way out to parsing code - dotrap needs it */ static int evalstring(char *s, int flags); @@ -8807,8 +9112,11 @@ evaltree(union node *n, int flags) { int checkexit = 0; int (*evalfn)(union node *, int); + struct stackmark smark; int status = 0; + setstackmark(&smark); + if (n == NULL) { TRACE(("evaltree(NULL) called\n")); goto out; @@ -8828,6 +9136,9 @@ evaltree(union node *n, int flags) status = !evaltree(n->nnot.com, EV_TESTED); goto setstatus; case NREDIR: + errlinno = lineno = n->nredir.linno; + if (funcline) + lineno -= funcline - 1; expredir(n->nredir.redirect); pushredir(n->nredir.redirect); status = redirectsafe(n->nredir.redirect, REDIR_PUSH); @@ -8919,6 +9230,7 @@ evaltree(union node *n, int flags) if (flags & EV_EXIT) raise_exception(EXEXIT); + popstackmark(&smark); TRACE(("leaving evaltree (no interrupts)\n")); return exitstatus; } @@ -8979,10 +9291,12 @@ evalfor(union node *n, int flags) struct arglist arglist; union node *argp; struct strlist *sp; - struct stackmark smark; int status = 0; - setstackmark(&smark); + errlinno = lineno = n->ncase.linno; + if (funcline) + lineno -= funcline - 1; + arglist.list = NULL; arglist.lastp = &arglist.list; for (argp = n->nfor.args; argp; argp = argp->narg.next) { @@ -8999,7 +9313,6 @@ evalfor(union node *n, int flags) break; } loopnest--; - popstackmark(&smark); return status; } @@ -9010,10 +9323,12 @@ evalcase(union node *n, int flags) union node *cp; union node *patp; struct arglist arglist; - struct stackmark smark; int status = 0; - setstackmark(&smark); + errlinno = lineno = n->ncase.linno; + if (funcline) + lineno -= funcline - 1; + arglist.list = NULL; arglist.lastp = &arglist.list; expandarg(n->ncase.expr, &arglist, EXP_TILDE); @@ -9032,8 +9347,6 @@ evalcase(union node *n, int flags) } } out: - popstackmark(&smark); - return status; } @@ -9047,6 +9360,10 @@ evalsubshell(union node *n, int flags) int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */ int status; + errlinno = lineno = n->nredir.linno; + if (funcline) + lineno -= funcline - 1; + expredir(n->nredir.redirect); if (!backgnd && (flags & EV_EXIT) && !may_have_traps) goto nofork; @@ -9169,7 +9486,7 @@ evalpipe(union node *n, int flags) if (lp->next) { if (pipe(pip) < 0) { close(prevfd); - ash_msg_and_raise_error("pipe call failed"); + ash_msg_and_raise_perror("can't create pipe"); } } if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) { @@ -9206,6 +9523,11 @@ evalpipe(union node *n, int flags) return status; } +/* setinteractive needs this forward reference */ +#if EDITING_HAS_get_exe_name +static const char *get_builtin_name(int i) FAST_FUNC; +#endif + /* * Controls whether the shell is interactive or not. */ @@ -9220,8 +9542,8 @@ setinteractive(int on) setsignal(SIGINT); setsignal(SIGQUIT); setsignal(SIGTERM); -#if !ENABLE_FEATURE_SH_EXTRA_QUIET if (is_interactive > 1) { +#if !ENABLE_FEATURE_SH_EXTRA_QUIET /* Looks like they want an interactive shell */ static smallint did_banner; @@ -9235,8 +9557,16 @@ setinteractive(int on) ); did_banner = 1; } - } #endif +#if ENABLE_FEATURE_EDITING + if (!line_input_state) { + line_input_state = new_line_input_t(FOR_SHELL | WITH_PATH_LOOKUP); +# if EDITING_HAS_get_exe_name + line_input_state->get_exe_name = get_builtin_name; +# endif + } +#endif + } } static void @@ -9248,10 +9578,12 @@ optschanged(void) setinteractive(iflag); setjobctl(mflag); #if ENABLE_FEATURE_EDITING_VI - if (viflag) - line_input_state->flags |= VI_MODE; - else - line_input_state->flags &= ~VI_MODE; + if (line_input_state) { + if (viflag) + line_input_state->flags |= VI_MODE; + else + line_input_state->flags &= ~VI_MODE; + } #else viflag = 0; /* forcibly keep the option off */ #endif @@ -9354,8 +9686,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) struct jmploc *volatile savehandler; struct jmploc jmploc; int e; + int savefuncline; saveparam = shellparam; + savefuncline = funcline; savehandler = exception_handler; e = setjmp(jmploc.loc); if (e) { @@ -9365,7 +9699,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) exception_handler = &jmploc; shellparam.malloced = 0; func->count++; - funcnest++; + funcline = func->n.ndefun.linno; INT_ON; shellparam.nparam = argc - 1; shellparam.p = argv + 1; @@ -9373,12 +9707,10 @@ 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); + evaltree(func->n.ndefun.body, flags & EV_TESTED); funcdone: INT_OFF; - funcnest--; + funcline = savefuncline; freefunc(func); freeparam(&shellparam); shellparam = saveparam; @@ -9700,9 +10032,18 @@ find_builtin(const char *name) return bp; } +#if EDITING_HAS_get_exe_name +static const char * FAST_FUNC +get_builtin_name(int i) +{ + return /*i >= 0 &&*/ i < ARRAY_SIZE(builtintab) ? builtintab[i].name + 1 : NULL; +} +#endif + /* * Execute a simple command. */ +static void unwindfiles(struct parsefile *stop); static int isassignment(const char *p) { @@ -9725,8 +10066,8 @@ evalcommand(union node *cmd, int flags) "\0\0", bltincmd /* why three NULs? */ }; struct localvar_list *localvar_stop; + struct parsefile *file_stop; struct redirtab *redir_stop; - struct stackmark smark; union node *argp; struct arglist arglist; struct arglist varlist; @@ -9742,10 +10083,14 @@ evalcommand(union node *cmd, int flags) char **nargv; smallint cmd_is_exec; + errlinno = lineno = cmd->ncmd.linno; + if (funcline) + lineno -= funcline - 1; + /* First expand the arguments. */ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); - setstackmark(&smark); localvar_stop = pushlocalvars(); + file_stop = g_parsefile; back_exitstatus = 0; cmdentry.cmdtype = CMDBUILTIN; @@ -9787,7 +10132,7 @@ evalcommand(union node *cmd, int flags) *nargv = NULL; lastarg = NULL; - if (iflag && funcnest == 0 && argc > 0) + if (iflag && funcline == 0 && argc > 0) lastarg = nargv[-1]; expredir(cmd->ncmd.redirect); @@ -9917,7 +10262,9 @@ evalcommand(union node *cmd, int flags) switch (cmdentry.cmdtype) { default: { -#if ENABLE_FEATURE_SH_NOFORK +#if ENABLE_FEATURE_SH_STANDALONE \ + && ENABLE_FEATURE_SH_NOFORK \ + && NUM_APPLETS > 1 /* (1) BUG: if variables are set, we need to fork, or save/restore them * around run_nofork_applet() call. * (2) Should this check also be done in forkshell()? @@ -9926,7 +10273,11 @@ evalcommand(union node *cmd, int flags) /* find_command() encodes applet_no as (-2 - applet_no) */ int applet_no = (- cmdentry.u.index - 2); if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { - listsetvar(varlist.list, VEXPORT|VSTACK); + char **sv_environ; + + INT_OFF; + sv_environ = environ; + environ = listvars(VEXPORT, VUNSET, varlist.list, /*end:*/ NULL); /* * Run _main(). * Signals (^C) can't interrupt here. @@ -9935,8 +10286,8 @@ evalcommand(union node *cmd, int flags) * and/or wait for user input ineligible for NOFORK: * for example, "yes" or "rm" (rm -i waits for input). */ - INT_OFF; status = run_nofork_applet(applet_no, argv); + environ = sv_environ; /* * Try enabling NOFORK for "yes" applet. * ^C _will_ stop it (write returns EINTR), @@ -9998,7 +10349,6 @@ evalcommand(union node *cmd, int flags) goto readstatus; case CMDFUNCTION: - poplocalvars(1); /* See above for the rationale */ dowait(DOWAIT_NONBLOCK, NULL); if (evalfun(cmdentry.u.func, argc, argv, flags)) @@ -10012,6 +10362,7 @@ evalcommand(union node *cmd, int flags) if (cmd->ncmd.redirect) popredir(/*drop:*/ cmd_is_exec); unwindredir(redir_stop); + unwindfiles(file_stop); unwindlocalvars(localvar_stop); if (lastarg) { /* dsl: I think this is intended to be used to support @@ -10020,7 +10371,6 @@ evalcommand(union node *cmd, int flags) */ setvar0("_", lastarg); } - popstackmark(&smark); return status; } @@ -10215,13 +10565,11 @@ preadfd(void) else { # if ENABLE_ASH_IDLE_TIMEOUT int timeout = -1; - if (iflag) { - const char *tmout_var = lookupvar("TMOUT"); - if (tmout_var) { - timeout = atoi(tmout_var) * 1000; - if (timeout <= 0) - timeout = -1; - } + const char *tmout_var = lookupvar("TMOUT"); + if (tmout_var) { + timeout = atoi(tmout_var) * 1000; + if (timeout <= 0) + timeout = -1; } line_input_state->timeout = timeout; # endif @@ -10469,6 +10817,34 @@ pgetc_eatbnl(void) return c; } +struct synstack { + smalluint syntax; + uint8_t innerdq :1; + uint8_t varpushed :1; + uint8_t dblquote :1; + int varnest; /* levels of variables expansion */ + int dqvarnest; /* levels of variables expansion within double quotes */ + int parenlevel; /* levels of parens in arithmetic */ + struct synstack *prev; + struct synstack *next; +}; + +static void +synstack_push(struct synstack **stack, struct synstack *next, int syntax) +{ + memset(next, 0, sizeof(*next)); + next->syntax = syntax; + next->next = *stack; + (*stack)->prev = next; + *stack = next; +} + +static ALWAYS_INLINE void +synstack_pop(struct synstack **stack) +{ + *stack = (*stack)->next; +} + /* * To handle the "." command, a stack of input files is used. Pushfile * adds a new entry to the stack and popfile restores the previous level. @@ -10506,14 +10882,20 @@ popfile(void) INT_ON; } +static void +unwindfiles(struct parsefile *stop) +{ + while (g_parsefile != stop) + popfile(); +} + /* * Return to top level. */ static void popallfiles(void) { - while (g_parsefile != &basepf) - popfile(); + unwindfiles(&basepf); } /* @@ -10559,17 +10941,18 @@ setinputfile(const char *fname, int flags) int fd; INT_OFF; - fd = open(fname, O_RDONLY); + fd = open(fname, O_RDONLY | O_CLOEXEC); if (fd < 0) { if (flags & INPUT_NOFILE_OK) goto out; exitstatus = 127; - ash_msg_and_raise_error("can't open '%s'", fname); + ash_msg_and_raise_perror("can't open '%s'", fname); } if (fd < 10) fd = savefd(fd); - else + else if (O_CLOEXEC == 0) /* old libc */ close_on_exec_on(fd); + setinputfd(fd, flags & INPUT_PUSH_FILE); out: INT_ON; @@ -10725,6 +11108,8 @@ plus_minus_o(char *name, int val) return 1; } for (i = 0; i < NOPTS; i++) { + if (optnames(i)[0] == '\0') + continue; if (val) { out1fmt("%-16s%s\n", optnames(i), optlist[i] ? "on" : "off"); } else { @@ -10739,7 +11124,7 @@ setoption(int flag, int val) int i; for (i = 0; i < NOPTS; i++) { - if (optletters(i) == flag) { + if (optletters(i) == flag && optnames(i)[0] != '\0') { optlist[i] = val; return; } @@ -10747,14 +11132,17 @@ setoption(int flag, int val) ash_msg_and_raise_error("illegal option %c%c", val ? '-' : '+', flag); /* NOTREACHED */ } +/* If login_sh is not NULL, we are called to parse command line opts, + * not "set -opts" + */ static int -options(int cmdline, int *login_sh) +options(int *login_sh) { char *p; int val; int c; - if (cmdline) + if (login_sh) minusc = NULL; while ((p = *argptr) != NULL) { c = *p++; @@ -10765,7 +11153,7 @@ options(int cmdline, int *login_sh) if (c == '-') { val = 1; if (p[0] == '\0' || LONE_DASH(p)) { - if (!cmdline) { + if (!login_sh) { /* "-" means turn off -x and -v */ if (p[0] == '\0') xflag = vflag = 0; @@ -10778,26 +11166,40 @@ options(int cmdline, int *login_sh) } /* first char was + or - */ while ((c = *p++) != '\0') { - /* bash 3.2 indeed handles -c CMD and +c CMD the same */ - if (c == 'c' && cmdline) { - minusc = p; /* command is after shell args */ - } else if (c == 'o') { + if (login_sh) { + /* bash 3.2 indeed handles -c CMD and +c CMD the same */ + if (c == 'c') { + minusc = p; /* command is after shell args */ + cflag = 1; + continue; + } + if (c == 's') { /* -s, +s */ + sflag = 1; + continue; + } + if (c == 'i') { /* -i, +i */ + iflag = 1; + continue; + } + if (c == 'l') { + *login_sh = 1; /* -l or +l == --login */ + continue; + } + /* bash does not accept +-login, we also won't */ + if (val && (c == '-')) { /* long options */ + if (strcmp(p, "login") == 0) { + *login_sh = 1; + } + break; + } + } + if (c == 'o') { if (plus_minus_o(*argptr, val)) { /* it already printed err message */ return 1; /* error */ } if (*argptr) argptr++; - } else if (cmdline && (c == 'l')) { /* -l or +l == --login */ - if (login_sh) - *login_sh = 1; - /* bash does not accept +-login, we also won't */ - } else if (cmdline && val && (c == '-')) { /* long options */ - if (strcmp(p, "login") == 0) { - if (login_sh) - *login_sh = 1; - } - break; } else { setoption(c, val); } @@ -10819,7 +11221,7 @@ shiftcmd(int argc UNUSED_PARAM, char **argv) if (argv[1]) n = number(argv[1]); if (n > shellparam.nparam) - n = 0; /* bash compat, was = shellparam.nparam; */ + return 1; INT_OFF; shellparam.nparam -= n; for (ap1 = shellparam.p; --n >= 0; ap1++) { @@ -10850,7 +11252,7 @@ showvars(const char *sep_prefix, int on, int off) const char *sep; char **ep, **epend; - ep = listvars(on, off, &epend); + ep = listvars(on, off, /*strlist:*/ NULL, &epend); qsort(ep, epend - ep, sizeof(char *), vpcmp); sep = *sep_prefix ? " " : sep_prefix; @@ -10859,9 +11261,17 @@ showvars(const char *sep_prefix, int on, int off) const char *p; const char *q; - p = strchrnul(*ep, '='); + p = endofname(*ep); +/* Used to have simple "p = strchrnul(*ep, '=')" here instead, but this + * makes "export -p" to have output not suitable for "eval": + * import os + * os.environ["test-test"]="test" + * if os.fork() == 0: + * os.execv("ash", [ 'ash', '-c', 'eval $(export -p); echo OK' ]) # fixes this + * os.execv("ash", [ 'ash', '-c', 'env | grep test-test' ]) + */ q = nullstr; - if (*p) + if (*p == '=') q = single_quote(++p); out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); } @@ -10880,7 +11290,7 @@ setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return showvars(nullstr, 0, VUNSET); INT_OFF; - retval = options(/*cmdline:*/ 0, NULL); + retval = options(/*login_sh:*/ NULL); if (retval == 0) { /* if no parse error... */ optschanged(); if (*argptr != NULL) { @@ -10911,6 +11321,32 @@ change_random(const char *value) } #endif +#if BASH_EPOCH_VARS +static void FAST_FUNC +change_epoch(struct var *vepoch, const char *fmt) +{ + struct timeval tv; + char buffer[sizeof("%lu.nnnnnn") + sizeof(long)*3]; + + gettimeofday(&tv, NULL); + sprintf(buffer, fmt, (unsigned long)tv.tv_sec, (unsigned)tv.tv_usec); + setvar(vepoch->var_text, buffer, VNOFUNC); + vepoch->flags &= ~VNOFUNC; +} + +static void FAST_FUNC +change_seconds(const char *value UNUSED_PARAM) +{ + change_epoch(&vepochs, "%lu"); +} + +static void FAST_FUNC +change_realtime(const char *value UNUSED_PARAM) +{ + change_epoch(&vepochr, "%lu.%06u"); +} +#endif + #if ENABLE_ASH_GETOPTS static int getopts(char *optstr, char *optvar, char **optfirst) @@ -10937,6 +11373,7 @@ getopts(char *optstr, char *optvar, char **optfirst) p = *optnext; if (p == NULL || *p != '-' || *++p == '\0') { atend: + unsetvar("OPTARG"); p = NULL; done = 1; goto out; @@ -10949,7 +11386,11 @@ getopts(char *optstr, char *optvar, char **optfirst) c = *p++; for (q = optstr; *q != c;) { if (*q == '\0') { - if (optstr[0] == ':') { + /* OPTERR is a bashism */ + const char *cp = lookupvar("OPTERR"); + if ((cp && LONE_CHAR(cp, '0')) + || (optstr[0] == ':') + ) { sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ setvar0("OPTARG", sbuf); @@ -10966,7 +11407,11 @@ getopts(char *optstr, char *optvar, char **optfirst) if (*++q == ':') { if (*p == '\0' && (p = *optnext) == NULL) { - if (optstr[0] == ':') { + /* OPTERR is a bashism */ + const char *cp = lookupvar("OPTERR"); + if ((cp && LONE_CHAR(cp, '0')) + || (optstr[0] == ':') + ) { sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ setvar0("OPTARG", sbuf); @@ -11272,7 +11717,7 @@ parsefname(void) if (quoteflag == 0) n->type = NXHERE; TRACE(("Here document %d\n", n->type)); - rmescapes(wordtext, 0); + rmescapes(wordtext, 0, NULL); here->eofmark = wordtext; here->next = NULL; if (heredoclist == NULL) @@ -11297,6 +11742,7 @@ simplecmd(void) union node *vars, **vpp; union node **rpp, *redir; int savecheckkwd; + int savelinno; #if BASH_TEST2 smallint double_brackets_flag = 0; #endif @@ -11310,6 +11756,7 @@ simplecmd(void) rpp = &redir; savecheckkwd = CHKALIAS; + savelinno = g_parsefile->linno; for (;;) { int t; checkkwd = savecheckkwd; @@ -11365,10 +11812,12 @@ simplecmd(void) case TLP: function_flag = 0; break; +# if BASH_TEST2 case TWORD: if (strcmp("[[", wordtext) == 0) goto do_func; /* fall through */ +# endif default: raise_error_unexpected_syntax(-1); } @@ -11399,7 +11848,9 @@ simplecmd(void) } n->type = NDEFUN; checkkwd = CHKNL | CHKKWD | CHKALIAS; - n->narg.next = parse_command(); + n->ndefun.text = n->narg.text; + n->ndefun.linno = g_parsefile->linno; + n->ndefun.body = parse_command(); return n; } IF_BASH_FUNCTION(function_flag = 0;) @@ -11414,7 +11865,9 @@ simplecmd(void) *vpp = NULL; *rpp = NULL; n = stzalloc(sizeof(struct ncmd)); - n->type = NCMD; + if (NCMD != 0) + n->type = NCMD; + n->ncmd.linno = savelinno; n->ncmd.args = args; n->ncmd.assign = vars; n->ncmd.redirect = redir; @@ -11430,10 +11883,13 @@ parse_command(void) union node *redir, **rpp; union node **rpp2; int t; + int savelinno; redir = NULL; rpp2 = &redir; + savelinno = g_parsefile->linno; + switch (readtoken()) { default: raise_error_unexpected_syntax(-1); @@ -11484,6 +11940,7 @@ parse_command(void) raise_error_syntax("bad for loop variable"); n1 = stzalloc(sizeof(struct nfor)); n1->type = NFOR; + n1->nfor.linno = savelinno; n1->nfor.var = wordtext; checkkwd = CHKNL | CHKKWD | CHKALIAS; if (readtoken() == TIN) { @@ -11524,6 +11981,7 @@ parse_command(void) case TCASE: n1 = stzalloc(sizeof(struct ncase)); n1->type = NCASE; + n1->ncase.linno = savelinno; if (readtoken() != TWORD) raise_error_unexpected_syntax(TWORD); n1->ncase.expr = n2 = stzalloc(sizeof(struct narg)); @@ -11575,6 +12033,7 @@ parse_command(void) case TLP: n1 = stzalloc(sizeof(struct nredir)); n1->type = NSUBSHELL; + n1->nredir.linno = savelinno; n1->nredir.n = list(0); /*n1->nredir.redirect = NULL; - stzalloc did it */ t = TRP; @@ -11608,6 +12067,7 @@ parse_command(void) if (n1->type != NSUBSHELL) { n2 = stzalloc(sizeof(struct nredir)); n2->type = NREDIR; + n2->nredir.linno = savelinno; n2->nredir.n = n1; n1 = n2; } @@ -11698,21 +12158,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) size_t len; struct nodelist *bqlist; smallint quotef; - smallint dblquote; smallint oldstyle; - IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ smallint pssyntax; /* we are expanding a prompt string */ - 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 */ - int dqvarnest; /* levels of variables expansion within double quotes */ - IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) + /* syntax stack */ + struct synstack synbase = { }; + struct synstack *synstack = &synbase; - startlinno = g_parsefile->linno; - bqlist = NULL; - quotef = 0; - IF_FEATURE_SH_MATH(prevsyntax = 0;) #if ENABLE_ASH_EXPAND_PRMT pssyntax = (syntax == PSSYNTAX); if (pssyntax) @@ -11720,11 +12172,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) #else pssyntax = 0; /* constant */ #endif - dblquote = (syntax == DQSYNTAX); - varnest = 0; - IF_FEATURE_SH_MATH(arinest = 0;) - IF_FEATURE_SH_MATH(parenlevel = 0;) - dqvarnest = 0; + synstack->syntax = syntax; + + if (syntax == DQSYNTAX) + synstack->dblquote = 1; + quotef = 0; + bqlist = NULL; STARTSTACKSTR(out); loop: @@ -11732,13 +12185,16 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) CHECKEND(); /* set c to PEOF if at end of here document */ for (;;) { /* until end of line or end of word */ CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ - switch (SIT(c, syntax)) { + switch (SIT(c, synstack->syntax)) { case CNL: /* '\n' */ - if (syntax == BASESYNTAX) + if (synstack->syntax == BASESYNTAX + && !synstack->varnest + ) { goto endword; /* exit outer loop */ + } USTPUTC(c, out); nlprompt(); - c = pgetc(); + c = synstack->syntax == SQSYNTAX ? pgetc() : pgetc_eatbnl(); goto loop; /* continue outer loop */ case CWORD: USTPUTC(c, out); @@ -11754,13 +12210,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) if (c & 0x100) { /* Unknown escape. Encode as '\z' */ c = (unsigned char)c; - if (eofmark == NULL || dblquote) + if (eofmark == NULL || synstack->dblquote) USTPUTC(CTLESC, out); USTPUTC('\\', out); } } #endif - if (eofmark == NULL || dblquote) + if (!eofmark || synstack->dblquote || synstack->varnest) USTPUTC(CTLESC, out); USTPUTC(c, out); break; @@ -11770,20 +12226,22 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) USTPUTC(CTLESC, out); USTPUTC('\\', out); pungetc(); - } else if (c == '\n') { - nlprompt(); } else { if (pssyntax && c == '$') { USTPUTC(CTLESC, out); USTPUTC('\\', out); } - /* Backslash is retained if we are in "str" and next char isn't special */ - if (dblquote + /* Backslash is retained if we are in "str" + * and next char isn't dquote-special. + */ + if (synstack->dblquote && c != '\\' && c != '`' && c != '$' - && (c != '"' || eofmark != NULL) + && (c != '"' || (eofmark != NULL && !synstack->varnest)) + && (c != '}' || !synstack->varnest) ) { + USTPUTC(CTLESC, out); /* protect '\' from glob */ USTPUTC('\\', out); } USTPUTC(CTLESC, out); @@ -11792,56 +12250,62 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } break; case CSQUOTE: - syntax = SQSYNTAX; + synstack->syntax = SQSYNTAX; quotemark: if (eofmark == NULL) { USTPUTC(CTLQUOTEMARK, out); } break; case CDQUOTE: - syntax = DQSYNTAX; - dblquote = 1; + synstack->syntax = DQSYNTAX; + synstack->dblquote = 1; + toggledq: + if (synstack->varnest) + synstack->innerdq ^= 1; goto quotemark; case CENDQUOTE: IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;) - if (eofmark != NULL && varnest == 0) { + if (eofmark != NULL && synstack->varnest == 0) { USTPUTC(c, out); - } else { - if (dqvarnest == 0) { - syntax = BASESYNTAX; - dblquote = 0; - } - quotef = 1; - goto quotemark; + break; } - break; + + if (synstack->dqvarnest == 0) { + synstack->syntax = BASESYNTAX; + synstack->dblquote = 0; + } + + quotef = 1; + + if (c == '"') + goto toggledq; + + goto quotemark; case CVAR: /* '$' */ PARSESUB(); /* parse substitution */ break; case CENDVAR: /* '}' */ - if (varnest > 0) { - varnest--; - if (dqvarnest > 0) { - dqvarnest--; - } + if (!synstack->innerdq && synstack->varnest > 0) { + if (!--synstack->varnest && synstack->varpushed) + synstack_pop(&synstack); + else if (synstack->dqvarnest > 0) + synstack->dqvarnest--; c = CTLENDVAR; } USTPUTC(c, out); break; #if ENABLE_FEATURE_SH_MATH case CLP: /* '(' in arithmetic */ - parenlevel++; + synstack->parenlevel++; USTPUTC(c, out); break; case CRP: /* ')' in arithmetic */ - if (parenlevel > 0) { - parenlevel--; + if (synstack->parenlevel > 0) { + synstack->parenlevel--; } else { if (pgetc_eatbnl() == ')') { c = CTLENDARI; - if (--arinest == 0) { - syntax = prevsyntax; - } + synstack_pop(&synstack); } else { /* * unbalanced parens @@ -11854,6 +12318,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) break; #endif case CBQUOTE: /* '`' */ + if (checkkwd & CHKEOFMARK) { + quotef = 1; + USTPUTC('`', out); + break; + } + PARSEBACKQOLD(); break; case CENDFILE: @@ -11861,7 +12331,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) case CIGN: break; default: - if (varnest == 0) { + if (synstack->varnest == 0) { #if BASH_REDIR_OUTPUT if (c == '&') { //Can't call pgetc_eatbnl() here, this requires three-deep pungetc() @@ -11875,18 +12345,17 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) IF_ASH_ALIAS(if (c != PEOA)) USTPUTC(c, out); } - c = pgetc(); + c = synstack->syntax == SQSYNTAX ? pgetc() : pgetc_eatbnl(); } /* for (;;) */ endword: #if ENABLE_FEATURE_SH_MATH - if (syntax == ARISYNTAX) + if (synstack->syntax == ARISYNTAX) raise_error_syntax("missing '))'"); #endif - if (syntax != BASESYNTAX && eofmark == NULL) + if (synstack->syntax != BASESYNTAX && eofmark == NULL) raise_error_syntax("unterminated quoted string"); - if (varnest != 0) { - startlinno = g_parsefile->linno; + if (synstack->varnest != 0) { /* { */ raise_error_syntax("missing '}'"); } @@ -11939,7 +12408,13 @@ checkend: { for (p = eofmark; STPUTC(c, out), *p; p++) { if (c != *p) goto more_heredoc; - + /* FIXME: fails for backslash-newlined terminator: + * cat <') { np->nfile.fd = 1; - c = pgetc(); + c = pgetc_eatbnl(); if (c == '>') np->type = NAPPEND; else if (c == '|') @@ -12009,7 +12484,7 @@ parseredir: { #endif else { /* c == '<' */ /*np->nfile.fd = 0; - stzalloc did it */ - c = pgetc(); + c = pgetc_eatbnl(); switch (c) { case '<': if (sizeof(struct nfile) != sizeof(struct nhere)) { @@ -12019,7 +12494,7 @@ parseredir: { np->type = NHERE; heredoc = stzalloc(sizeof(struct heredoc)); heredoc->here = np; - c = pgetc(); + c = pgetc_eatbnl(); if (c == '-') { heredoc->striptabs = 1; } else { @@ -12068,7 +12543,7 @@ parsesub: { || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) ) { #if BASH_DOLLAR_SQUOTE - if (syntax != DQSYNTAX && c == '\'') + if (synstack->syntax != DQSYNTAX && c == '\'') bash_dollar_squote = 1; else #endif @@ -12088,6 +12563,8 @@ parsesub: { } } else { /* $VAR, $, ${...}, or PEOA/PEOF */ + smalluint newsyn = synstack->syntax; + USTPUTC(CTLVAR, out); typeloc = out - (char *)stackblock(); STADJUST(1, out); @@ -12109,7 +12586,7 @@ parsesub: { STPUTC(c, out); c = pgetc_eatbnl(); } while (isdigit(c)); - } else if (is_special(c)) { + } else if (c != '}') { /* $[{[#]][}] */ int cc = c; @@ -12127,10 +12604,17 @@ parsesub: { cc = '#'; } } + + if (!is_special(cc)) { + if (subtype == VSLENGTH) + subtype = 0; + goto badsub; + } + USTPUTC(cc, out); - } else { + } else goto badsub; - } + if (c != '}' && subtype == VSLENGTH) { /* ${#VAR didn't end with } */ goto badsub; @@ -12140,6 +12624,8 @@ parsesub: { static const char types[] ALIGN1 = "}-+?="; /* ${VAR...} but not $VAR or ${#VAR} */ /* c == first char after VAR */ + int cc = c; + switch (c) { case ':': c = pgetc_eatbnl(); @@ -12164,21 +12650,24 @@ parsesub: { break; } case '%': - case '#': { - int cc = c; + case '#': subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); c = pgetc_eatbnl(); - if (c != cc) - goto badsub; - subtype++; + if (c == cc) + subtype++; + else + pungetc(); + + newsyn = BASESYNTAX; break; - } #if BASH_PATTERN_SUBST case '/': /* ${v/[/]pattern/repl} */ //TODO: encode pattern and repl separately. -// Currently ${v/$var_with_slash/repl} is horribly broken +// Currently cases like: v=1;echo ${v/$((1/1))/ONE} +// are broken (should print "ONE") subtype = VSREPLACE; + newsyn = BASESYNTAX; c = pgetc_eatbnl(); if (c != '/') goto badsub; @@ -12190,11 +12679,26 @@ parsesub: { badsub: pungetc(); } + + if (newsyn == ARISYNTAX) + newsyn = DQSYNTAX; + + if ((newsyn != synstack->syntax || synstack->innerdq) + && subtype != VSNORMAL + ) { + synstack_push(&synstack, + synstack->prev ?: alloca(sizeof(*synstack)), + newsyn); + + synstack->varpushed = 1; + synstack->dblquote = newsyn != BASESYNTAX; + } + ((unsigned char *)stackblock())[typeloc] = subtype; if (subtype != VSNORMAL) { - varnest++; - if (dblquote) - dqvarnest++; + synstack->varnest++; + if (synstack->dblquote) + synstack->dqvarnest++; } STPUTC('=', out); } @@ -12243,25 +12747,15 @@ parsebackq: { int pc; setprompt_if(needprompt, 2); - pc = pgetc(); + pc = pgetc_eatbnl(); switch (pc) { case '`': goto done; case '\\': - pc = pgetc(); - if (pc == '\n') { - nlprompt(); - /* - * If eating a newline, avoid putting - * the newline into the new character - * stream (via the STPUTC after the - * switch). - */ - continue; - } + pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */ if (pc != '\\' && pc != '`' && pc != '$' - && (!dblquote || pc != '"') + && (!synstack->dblquote || pc != '"') ) { STPUTC('\\', pout); } @@ -12272,7 +12766,6 @@ parsebackq: { case PEOF: IF_ASH_ALIAS(case PEOA:) - startlinno = g_parsefile->linno; raise_error_syntax("EOF in backquote substitution"); case '\n': @@ -12337,10 +12830,11 @@ parsebackq: { * Parse an arithmetic expansion (indicate start of one and set state) */ parsearith: { - if (++arinest == 1) { - prevsyntax = syntax; - syntax = ARISYNTAX; - } + + synstack_push(&synstack, + synstack->prev ?: alloca(sizeof(*synstack)), + ARISYNTAX); + synstack->dblquote = 1; USTPUTC(CTLARI, out); goto parsearith_return; } @@ -12354,8 +12848,6 @@ parsearith: { * quoted. * If the token is TREDIR, then we set redirnode to a structure containing * the redirection. - * In all cases, the variable startlinno is set to the number of the line - * on which the token starts. * * [Change comment: here documents and internal procedures] * [Readtoken shouldn't have any arguments. Perhaps we should make the @@ -12393,9 +12885,8 @@ xxreadtoken(void) return lasttoken; } setprompt_if(needprompt, 2); - startlinno = g_parsefile->linno; for (;;) { /* until token or start of word found */ - c = pgetc(); + c = pgetc_eatbnl(); if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA)) continue; @@ -12404,11 +12895,7 @@ xxreadtoken(void) continue; pungetc(); } else if (c == '\\') { - if (pgetc() != '\n') { - pungetc(); - break; /* return readtoken1(...) */ - } - nlprompt(); + break; /* return readtoken1(...) */ } else { const char *p; @@ -12423,7 +12910,7 @@ xxreadtoken(void) break; /* return readtoken1(...) */ if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) { - int cc = pgetc(); + int cc = pgetc_eatbnl(); if (cc == c) { /* double occurrence? */ p += xxreadtoken_doubles + 1; } else { @@ -12454,9 +12941,8 @@ xxreadtoken(void) return lasttoken; } setprompt_if(needprompt, 2); - startlinno = g_parsefile->linno; for (;;) { /* until token or start of word found */ - c = pgetc(); + c = pgetc_eatbnl(); switch (c) { case ' ': case '\t': IF_ASH_ALIAS(case PEOA:) @@ -12466,30 +12952,23 @@ xxreadtoken(void) continue; pungetc(); continue; - case '\\': - if (pgetc() == '\n') { - nlprompt(); - continue; - } - pungetc(); - goto breakloop; case '\n': nlnoprompt(); RETURN(TNL); case PEOF: RETURN(TEOF); case '&': - if (pgetc() == '&') + if (pgetc_eatbnl() == '&') RETURN(TAND); pungetc(); RETURN(TBACKGND); case '|': - if (pgetc() == '|') + if (pgetc_eatbnl() == '|') RETURN(TOR); pungetc(); RETURN(TPIPE); case ';': - if (pgetc() == ';') + if (pgetc_eatbnl() == ';') RETURN(TENDCASE); pungetc(); RETURN(TSEMI); @@ -12497,11 +12976,9 @@ xxreadtoken(void) RETURN(TLP); case ')': RETURN(TRP); - default: - goto breakloop; } + break; } - breakloop: return readtoken1(c, BASESYNTAX, (char *)NULL, 0); #undef RETURN } @@ -12612,9 +13089,12 @@ parseheredoc(void) heredoclist = NULL; while (here) { + tokpushback = 0; setprompt_if(needprompt, 2); - readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX, - here->eofmark, here->striptabs); + if (here->here->type == NHERE) + readtoken1(pgetc(), SQSYNTAX, here->eofmark, here->striptabs); + else + readtoken1(pgetc_eatbnl(), DQSYNTAX, here->eofmark, here->striptabs); n = stzalloc(sizeof(struct narg)); n->narg.type = NARG; /*n->narg.next = NULL; - stzalloc did it */ @@ -12631,24 +13111,54 @@ expandstr(const char *ps, int syntax_type) { union node n; int saveprompt; + struct parsefile *file_stop = g_parsefile; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; + const char *volatile result; + int err; /* XXX Fix (char *) cast. */ setinputstring((char *)ps); saveprompt = doprompt; doprompt = 0; - readtoken1(pgetc(), syntax_type, FAKEEOFMARK, 0); - doprompt = saveprompt; + result = ps; - popfile(); + SAVE_INT(saveint); + err = setjmp(jmploc.loc); + if (err) + goto out; + + /* readtoken1() might die horribly. + * Try a prompt with syntactically wrong command: + * PS1='$(date "+%H:%M:%S) > ' + */ + exception_handler = &jmploc; + readtoken1(pgetc(), syntax_type, FAKEEOFMARK, 0); n.narg.type = NARG; n.narg.next = NULL; n.narg.text = wordtext; n.narg.backquote = backquotelist; + /* expandarg() might fail too: + * PS1='$((123+))' + */ expandarg(&n, NULL, EXP_QUOTED); - return stackblock(); + result = stackblock(); + +out: + exception_handler = savehandler; + if (err && exception_type != EXERROR) + longjmp(exception_handler->loc, 1); + RESTORE_INT(saveint); + + doprompt = saveprompt; + /* Try: PS1='`xxx(`' */ + unwindfiles(file_stop); + + return result; } static inline int @@ -12706,7 +13216,7 @@ evalstring(char *s, int flags) exception_handler = savehandler; if (ex) - longjmp(exception_handler->loc, ex); + longjmp(exception_handler->loc, ex); return status; } @@ -12829,10 +13339,14 @@ find_dot_file(char *name) if (fullname != name) stunalloc(fullname); } + /* not found in PATH */ - /* not found in the PATH */ +#if ENABLE_ASH_BASH_SOURCE_CURDIR + return name; +#else ash_msg_and_raise_error("%s: not found", name); /* NOTREACHED */ +#endif } static int FAST_FUNC @@ -13087,8 +13601,21 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) /* We failed. If there was an entry for this command, delete it */ if (cmdp && updatetbl) delete_cmd_entry(); - if (act & DO_ERR) + if (act & DO_ERR) { +#if ENABLE_ASH_BASH_NOT_FOUND_HOOK + struct tblentry *hookp = cmdlookup("command_not_found_handle", 0); + if (hookp && hookp->cmdtype == CMDFUNCTION) { + char *argv[3]; + argv[0] = (char*) "command_not_found_handle"; + argv[1] = name; + argv[2] = NULL; + evalfun(hookp->param.func, 2, argv, 0); + entry->cmdtype = CMDUNKNOWN; + return; + } +#endif ash_msg("%s: %s", name, errmsg(e, "not found")); + } entry->cmdtype = CMDUNKNOWN; return; @@ -13230,7 +13757,8 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) static int FAST_FUNC historycmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { - show_history(line_input_state); + if (line_input_state) + show_history(line_input_state); return EXIT_SUCCESS; } #endif @@ -13343,21 +13871,23 @@ static const unsigned char timescmd_str[] ALIGN1 = { static int FAST_FUNC timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { - unsigned long clk_tck, s, t; + unsigned clk_tck; const unsigned char *p; struct tms buf; clk_tck = bb_clk_tck(); - times(&buf); + times(&buf); p = timescmd_str; do { + unsigned sec, frac; + unsigned long t; t = *(clock_t *)(((char *) &buf) + p[1]); - s = t / clk_tck; - t = t % clk_tck; - out1fmt("%lum%lu.%03lus%c", - s / 60, s % 60, - (t * 1000) / clk_tck, + sec = t / clk_tck; + frac = t % clk_tck; + out1fmt("%um%u.%03us%c", + sec / 60, sec % 60, + (frac * 1000) / clk_tck, p[0]); p += 2; } while (*p); @@ -13396,62 +13926,61 @@ letcmd(int argc UNUSED_PARAM, char **argv) * -p PROMPT Display PROMPT on stderr (if input is from tty) * -t SECONDS Timeout after SECONDS (tty or pipe only) * -u FD Read from given FD instead of fd 0 + * -d DELIM End on DELIM char, not newline * This uses unbuffered input, which may be avoidable in some cases. * TODO: bash also has: * -a ARRAY Read into array[0],[1],etc - * -d DELIM End on DELIM char, not newline * -e Use line editing (tty only) */ static int FAST_FUNC readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { - char *opt_n = NULL; - char *opt_p = NULL; - char *opt_t = NULL; - char *opt_u = NULL; - int read_flags = 0; + struct builtin_read_params params; const char *r; int i; - while ((i = nextopt("p:u:rt:n:s")) != '\0') { + memset(¶ms, 0, sizeof(params)); + + while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { switch (i) { case 'p': - opt_p = optionarg; + params.opt_p = optionarg; break; case 'n': - opt_n = optionarg; + params.opt_n = optionarg; break; case 's': - read_flags |= BUILTIN_READ_SILENT; + params.read_flags |= BUILTIN_READ_SILENT; break; case 't': - opt_t = optionarg; + params.opt_t = optionarg; break; case 'r': - read_flags |= BUILTIN_READ_RAW; + params.read_flags |= BUILTIN_READ_RAW; break; case 'u': - opt_u = optionarg; + params.opt_u = optionarg; + break; +#if BASH_READ_D + case 'd': + params.opt_d = optionarg; break; +#endif default: break; } } + params.argv = argptr; + params.setvar = setvar0; + params.ifs = bltinlookup("IFS"); /* can be NULL */ + /* "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, - bltinlookup("IFS"), /* can be NULL */ - read_flags, - opt_n, - opt_p, - opt_t, - opt_u - ); + r = shell_builtin_read(¶ms); INT_ON; if ((uintptr_t)r == 1 && errno == EINTR) { @@ -13511,8 +14040,8 @@ umaskcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) } } else { char *modestr = *argptr; - /* numeric umasks are taken as-is */ - /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */ + /* numeric umasks are taken as-is */ + /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */ if (!isdigit(modestr[0])) mask ^= 0777; mask = bb_parse_mode(modestr, mask); @@ -13545,7 +14074,8 @@ exitshell(void) int status; #if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT - save_history(line_input_state); + if (line_input_state) + save_history(line_input_state); #endif status = exitstatus; TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); @@ -13578,6 +14108,7 @@ init(void) { /* we will never free this */ basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); + basepf.linno = 1; sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */ setsignal(SIGCHLD); @@ -13593,12 +14124,23 @@ init(void) initvar(); for (envp = environ; envp && *envp; envp++) { - p = endofname(*envp); - if (p != *envp && *p == '=') { +/* Used to have + * p = endofname(*envp); + * if (p != *envp && *p == '=') { + * here to weed out badly-named variables, but this breaks + * scenarios where people do want them passed to children: + * import os + * os.environ["test-test"]="test" + * if os.fork() == 0: + * os.execv("ash", [ 'ash', '-c', 'eval $(export -p); echo OK' ]) # fixes this + * os.execv("ash", [ 'ash', '-c', 'env | grep test-test' ]) # breaks this + */ + if (strchr(*envp, '=')) { setvareq(*envp, VEXPORT|VTEXTFIXED); } } + setvareq((char*)defifsvar, VTEXTFIXED); setvareq((char*)defoptindvar, VTEXTFIXED); setvar0("PPID", utoa(getppid())); @@ -13628,7 +14170,7 @@ init(void) //usage:#define ash_trivial_usage -//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" +//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]" //usage:#define ash_full_usage "\n\n" //usage: "Unix shell interpreter" @@ -13645,13 +14187,17 @@ procargs(char **argv) xargv = argv; login_sh = xargv[0] && xargv[0][0] == '-'; +#if NUM_SCRIPTS > 0 + if (minusc) + goto setarg0; +#endif arg0 = xargv[0]; /* if (xargv[0]) - mmm, this is always true! */ xargv++; + argptr = xargv; for (i = 0; i < NOPTS; i++) optlist[i] = 2; - argptr = xargv; - if (options(/*cmdline:*/ 1, &login_sh)) { + if (options(&login_sh)) { /* it already printed err message */ raise_exception(EXERROR); } @@ -13662,8 +14208,13 @@ procargs(char **argv) ash_msg_and_raise_error(bb_msg_requires_arg, "-c"); sflag = 1; } - if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1)) + if (iflag == 2 /* no explicit -i given */ + && sflag == 1 /* -s given (or implied) */ + && !minusc /* bash compat: ash -sc 'echo $-' is not interactive (dash is) */ + && isatty(0) && isatty(1) /* we are on tty */ + ) { iflag = 1; + } if (mflag == 2) mflag = iflag; for (i = 0; i < NOPTS; i++) @@ -13752,7 +14303,12 @@ extern int etext(); * is used to figure out how far we had gotten. */ int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +#if NUM_SCRIPTS > 0 +int ash_main(int argc, char **argv) +#else int ash_main(int argc UNUSED_PARAM, char **argv) +#endif +/* note: 'argc' is used only if embedded scripts are enabled */ { volatile smallint state; struct jmploc jmploc; @@ -13772,9 +14328,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) monitor(4, etext, profile_buf, sizeof(profile_buf), 50); #endif -#if ENABLE_FEATURE_EDITING - line_input_state = new_line_input_t(FOR_SHELL | WITH_PATH_LOOKUP); -#endif state = 0; if (setjmp(jmploc.loc)) { smallint e; @@ -13806,6 +14359,12 @@ int ash_main(int argc UNUSED_PARAM, char **argv) init(); setstackmark(&smark); + +#if NUM_SCRIPTS > 0 + if (argc < 0) + /* Non-NULL minusc tells procargs that an embedded script is being run */ + minusc = get_script_content(-argc - 1); +#endif login_sh = procargs(argv); #if DEBUG TRACE(("Shell args: ")); @@ -13843,10 +14402,17 @@ int ash_main(int argc UNUSED_PARAM, char **argv) * Ensure we don't falsely claim that 0 (stdin) * is one of stacked source fds. * Testcase: ash -c 'exec 1>&0' must not complain. */ + // if (!sflag) g_parsefile->pf_fd = -1; // ^^ not necessary since now we special-case fd 0 // in save_fd_on_redirect() - evalstring(minusc, sflag ? 0 : EV_EXIT); + + // dash: evalstring(minusc, sflag ? 0 : EV_EXIT); + // The above makes + // ash -sc 'echo $-' + // continue reading input from stdin after running 'echo'. + // bash does not do this: it prints "hBcs" and exits. + evalstring(minusc, EV_EXIT); } if (sflag || minusc == NULL) {