X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fash.c;h=83cac3fb0a5eb315bacc52a6930829a717a298ba;hb=9a1a659707a18c29166c3e2977523102866d7aed;hp=3adb6d0d23f41592a0b9548d5ef9e18ec0389572;hpb=aa2959c90d9c3217ddb6f482b82fef7234ad9bde;p=oweals%2Fbusybox.git diff --git a/shell/ash.c b/shell/ash.c index 3adb6d0d2..83cac3fb0 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 @@ -148,16 +148,6 @@ //config: you to run the specified command or builtin, //config: even when there is a function with the same name. //config: -//config:config ASH_EMBEDDED_SCRIPTS -//config: bool "Embed scripts in the binary" -//config: default y -//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH -//config: help -//config: Allow scripts to be compressed and embedded in the busybox -//config: binary. The scripts should be placed in the 'embed' directory -//config: at build time. Like applets, scripts can be run as -//config: 'busybox SCRIPT ...' or by linking their name to the binary. -//config: //config:endif # ash options //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP)) @@ -191,7 +181,7 @@ #include #include /* for setting $HOSTNAME */ #include "busybox.h" /* for applet_names */ -#if ENABLE_ASH_EMBEDDED_SCRIPTS +#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS # include "embedded_scripts.h" #else # define NUM_SCRIPTS 0 @@ -230,10 +220,12 @@ #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() */ @@ -323,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", @@ -342,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) @@ -370,6 +384,7 @@ struct globals_misc { uint8_t exitstatus; /* exit status of last command */ uint8_t back_exitstatus;/* exit status of backquoted command */ smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */ + int savestatus; /* exit status of last command outside traps */ int rootpid; /* pid of main shell */ /* shell level: 0 for the main shell, 1 for its children, and so on */ int shlvl; @@ -389,11 +404,11 @@ struct globals_misc { volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */ volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */ - smallint exception_type; /* kind of exception (0..5) */ - /* exceptions */ + smallint exception_type; /* kind of exception: */ #define EXINT 0 /* SIGINT received */ #define EXERROR 1 /* a generic error */ -#define EXEXIT 4 /* exit the shell */ +#define EXEND 3 /* exit the shell */ +#define EXEXIT 4 /* exit the shell via exitcmd */ char nullstr[1]; /* zero length string */ @@ -405,21 +420,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 */ @@ -451,6 +467,7 @@ extern struct globals_misc *BB_GLOBAL_CONST 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 savestatus (G_misc.savestatus ) #define rootpid (G_misc.rootpid ) #define shlvl (G_misc.shlvl ) #define errlinno (G_misc.errlinno ) @@ -474,8 +491,9 @@ extern struct globals_misc *BB_GLOBAL_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(); \ + savestatus = -1; \ curdir = nullstr; \ physdir = nullstr; \ trap_ptr = trap; \ @@ -695,7 +713,7 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...) ret = vsnprintf(outbuf, length, fmt, ap); va_end(ap); INT_ON; - return ret; + return ret > (int)length ? length : ret; } static void @@ -1527,7 +1545,7 @@ extern struct globals_memstack *BB_GLOBAL_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; \ @@ -1660,15 +1678,16 @@ popstackmark(struct stackmark *mark) * part of the block that has been used. */ static void -growstackblock(void) +growstackblock(size_t min) { size_t newlen; newlen = g_stacknleft * 2; if (newlen < g_stacknleft) ash_msg_and_raise_error(bb_msg_memory_exhausted); - if (newlen < 128) - newlen += 128; + min = SHELL_ALIGN(min | 128); + if (newlen < min) + newlen += min; if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) { struct stack_block *sp; @@ -1718,10 +1737,18 @@ static void * growstackstr(void) { size_t len = stackblocksize(); - growstackblock(); + growstackblock(0); return (char *)stackblock() + len; } +static char * +growstackto(size_t len) +{ + if (stackblocksize() < len) + growstackblock(len); + return stackblock(); +} + /* * Called from CHECKSTRSPACE. */ @@ -1729,18 +1756,8 @@ static char * makestrspace(size_t newlen, char *p) { size_t len = p - g_stacknxt; - size_t size; - - for (;;) { - size_t nleft; - size = stackblocksize(); - nleft = size - len; - if (nleft >= newlen) - break; - growstackblock(); - } - return (char *)stackblock() + len; + return growstackto(len + newlen) + len; } static char * @@ -2062,6 +2079,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; @@ -2088,6 +2109,10 @@ static const struct { #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 }, @@ -2119,30 +2144,30 @@ extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var; #define linenovar (G_var.linenovar ) #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] +# 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 (&vps4)[1] -# define vlineno (&voptind)[1] -# if ENABLE_ASH_RANDOM_SUPPORT -# define vrandom (&vlineno)[1] -# endif -#else -# define vlineno (&vps4)[1] -# if ENABLE_ASH_RANDOM_SUPPORT -# define vrandom (&vlineno)[1] -# endif +# 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; \ @@ -2277,7 +2302,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. @@ -2373,6 +2398,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) @@ -2420,13 +2449,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; @@ -2448,24 +2476,6 @@ unsetvar(const char *s) setvar(s, NULL, 0); } -/* - * Process a linked list of variable assignments. - */ -static void -listsetvar(struct strlist *list_set_var, int flags) -{ - struct strlist *lp = list_set_var; - - if (!lp) - return; - INT_OFF; - do { - setvareq(lp->text, flags); - lp = lp->next; - } while (lp); - INT_ON; -} - /* * Generate a list of variables satisfying the given conditions. */ @@ -2529,51 +2539,102 @@ listvars(int on, int off, struct strlist *lp, char ***end) } -/* ============ Path search helper - * +/* ============ Path search helper */ +static const char * +legal_pathopt(const char *opt, const char *term, int magic) +{ + switch (magic) { + case 0: + opt = NULL; + break; + + case 1: + opt = prefix(opt, "builtin") ?: prefix(opt, "func"); + break; + + default: + opt += strcspn(opt, term); + break; + } + + if (opt && *opt == '%') + opt++; + + return opt; +} + +/* * The variable path (passed by reference) should be set to the start - * of the path before the first call; path_advance will update - * this value as it proceeds. Successive calls to path_advance will return + * of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return * the possible path expansions in sequence. If an option (indicated by * a percent sign) appears in the path entry then the global variable * pathopt will be set to point to it; otherwise pathopt will be set to * NULL. + * + * If magic is 0 then pathopt recognition will be disabled. If magic is + * 1 we shall recognise %builtin/%func. Otherwise we shall accept any + * pathopt. */ -static const char *pathopt; /* set by path_advance */ +static const char *pathopt; /* set by padvance */ -static char * -path_advance(const char **path, const char *name) +static int +padvance_magic(const char **path, const char *name, int magic) { + const char *term = "%:"; + const char *lpathopt; const char *p; char *q; const char *start; + size_t qlen; size_t len; if (*path == NULL) - return NULL; + return -1; + + lpathopt = NULL; start = *path; - for (p = start; *p && *p != ':' && *p != '%'; p++) - continue; - len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ - while (stackblocksize() < len) - growstackblock(); - q = stackblock(); - if (p != start) { - q = mempcpy(q, start, p - start); - *q++ = '/'; + + if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) { + lpathopt = start + 1; + start = p; + term = ":"; } - strcpy(q, name); - pathopt = NULL; + + len = strcspn(start, term); + p = start + len; + if (*p == '%') { - pathopt = ++p; - while (*p && *p != ':') - p++; + size_t extra = strchrnul(p, ':') - p; + + if (legal_pathopt(p + 1, term, magic)) + lpathopt = p + 1; + else + len += extra; + + p += extra; } - if (*p == ':') - *path = p + 1; - else - *path = NULL; - return stalloc(len); + + pathopt = lpathopt; + *path = *p == ':' ? p + 1 : NULL; + + /* "2" is for '/' and '\0' */ + qlen = len + strlen(name) + 2; + q = growstackto(qlen); + + if (len) { + q = mempcpy(q, start, len); + *q++ = '/'; + } + strcpy(q, name); + + return qlen; +} + +static int +padvance(const char **path, const char *name) +{ + return padvance_magic(path, name, 1); } @@ -2814,6 +2875,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) char c; struct stat statb; int flags; + int len; flags = cdopt(); dest = *argptr; @@ -2843,9 +2905,10 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) if (!*dest) dest = "."; path = bltinlookup("CDPATH"); - while (path) { - c = *path; - p = path_advance(&path, dest); + while (p = path, (len = padvance(&path, dest)) >= 0) { + c = *p; + p = stalloc(len); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { if (c && c != ':') flags |= CD_PRINT; @@ -3533,7 +3596,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 @@ -3729,8 +3792,6 @@ static struct job *jobtab; //5 static unsigned njobs; //4 /* current job */ 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 @@ -4174,12 +4235,11 @@ fg_bgcmd(int argc UNUSED_PARAM, char **argv) #endif static int -sprint_status48(char *s, int status, int sigonly) +sprint_status48(char *os, int status, int sigonly) { - int col; + char *s = os; int st; - col = 0; if (!WIFEXITED(status)) { #if JOBS if (WIFSTOPPED(status)) @@ -4197,17 +4257,17 @@ sprint_status48(char *s, int status, int sigonly) } st &= 0x7f; //TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata - col = fmtstr(s, 32, strsignal(st)); + //s = stpncpy(s, strsignal(st), 32); //not all libc have stpncpy() + s += fmtstr(s, 32, strsignal(st)); if (WCOREDUMP(status)) { - strcpy(s + col, " (core dumped)"); - col += sizeof(" (core dumped)")-1; + s = stpcpy(s, " (core dumped)"); } } else if (!sigonly) { st = WEXITSTATUS(status); - col = fmtstr(s, 16, (st ? "Done(%d)" : "Done"), st); + s += fmtstr(s, 16, (st ? "Done(%d)" : "Done"), st); } out: - return col; + return s - os; } static int @@ -4228,9 +4288,20 @@ 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); - while (!got_sigchld && !pending_sig) + sigprocmask2(SIG_SETMASK, &mask); /* mask is updated */ + while (!got_sigchld && !pending_sig) { sigsuspend(&mask); + /* ^^^ add "sigdelset(&mask, SIGCHLD);" before sigsuspend + * to make sure SIGCHLD is not masked off? + * It was reported that this: + * fn() { : | return; } + * shopt -s lastpipe + * fn + * exec ash SCRIPT + * under bash 4.4.23 runs SCRIPT with SIGCHLD masked, + * making "wait" commands in SCRIPT block forever. + */ + } sigprocmask(SIG_SETMASK, &mask, NULL); #else /* unsafe: a signal can set pending_sig after check, but before pause() */ while (!got_sigchld && !pending_sig) @@ -4246,14 +4317,21 @@ 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) +waitone(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)); @@ -4290,10 +4368,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; @@ -4345,13 +4423,16 @@ dowait(int block, struct job *job) goto out; } /* The process wasn't found in job list */ -#if JOBS - if (!WIFSTOPPED(status)) - jobless--; -#endif 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; @@ -4366,6 +4447,20 @@ dowait(int block, struct job *job) return pid; } +static int +dowait(int block, struct job *jp) +{ + int pid = block == DOWAIT_NONBLOCK ? got_sigchld : 1; + + while (jp ? jp->state == JOBRUNNING : pid > 0) { + if (!jp) + got_sigchld = 0; + pid = waitone(block, jp); + } + + return pid; +} + #if JOBS static void showjob(struct job *jp, int mode) @@ -4454,8 +4549,7 @@ showjobs(int mode) TRACE(("showjobs(0x%x) called\n", mode)); /* Handle all finished jobs */ - while (dowait(DOWAIT_NONBLOCK, NULL) > 0) - continue; + dowait(DOWAIT_NONBLOCK, NULL); for (jp = curjob; jp; jp = jp->prev_job) { if (!(mode & SHOW_CHANGED) || jp->changed) { @@ -4534,15 +4628,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; @@ -4558,13 +4661,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); - /* if child sends us a signal *and immediately exits*, - * dowait() returns pid > 0. Check this case, - * not "if (dowait() < 0)"! - */ +#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 } } @@ -4584,11 +4705,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv) job = getjob(*argv, 0); } /* loop until process terminated or stopped */ - while (job->state == JOBRUNNING) { - dowait(DOWAIT_BLOCK_OR_SIG, NULL); - if (pending_sig) - goto sigout; - } + dowait(DOWAIT_BLOCK_OR_SIG, NULL); + if (pending_sig) + goto sigout; job->waited = 1; retval = getstatus(job); repeat: ; @@ -4730,7 +4849,8 @@ cmdputs(const char *s) str = "${"; goto dostr; case CTLENDVAR: - str = "\"}" + !(quoted & 1); + str = "\"}"; + str += !(quoted & 1); quoted >>= 1; subtype = 0; goto dostr; @@ -5139,7 +5259,6 @@ forkchild(struct job *jp, union node *n, int mode) #endif for (jp = curjob; jp; jp = jp->prev_job) freejob(jp); - jobless = 0; } /* Called after fork(), in parent */ @@ -5150,13 +5269,8 @@ static void forkparent(struct job *jp, union node *n, int mode, pid_t pid) { TRACE(("In parent shell: child = %d\n", pid)); - if (!jp) { - /* jp is NULL when called by openhere() for heredoc support */ - while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0) - continue; - jobless++; + if (!jp) /* jp is NULL when called by openhere() for heredoc support */ return; - } #if JOBS if (mode != FORK_NOJOB && jp->jobctl) { int pgrp; @@ -5233,43 +5347,41 @@ waitforjob(struct job *jp) { int st; - TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0)); - INT_OFF; - while (jp->state == JOBRUNNING) { - /* In non-interactive shells, we _can_ get - * a keyboard signal here and be EINTRed, - * but we just loop back, waiting for command to complete. - * - * man bash: - * "If bash is waiting for a command to complete and receives - * a signal for which a trap has been set, the trap - * will not be executed until the command completes." - * - * Reality is that even if trap is not set, bash - * will not act on the signal until command completes. - * Try this. sleep5intoff.c: - * #include - * #include - * int main() { - * sigset_t set; - * sigemptyset(&set); - * sigaddset(&set, SIGINT); - * sigaddset(&set, SIGQUIT); - * sigprocmask(SIG_BLOCK, &set, NULL); - * sleep(5); - * return 0; - * } - * $ bash -c './sleep5intoff; echo hi' - * ^C^C^C^C <--- pressing ^C once a second - * $ _ - * $ bash -c './sleep5intoff; echo hi' - * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT) - * $ _ - */ - dowait(DOWAIT_BLOCK, jp); - } - INT_ON; + /* In non-interactive shells, we _can_ get + * a keyboard signal here and be EINTRed, but we just loop + * inside dowait(), waiting for command to complete. + * + * man bash: + * "If bash is waiting for a command to complete and receives + * a signal for which a trap has been set, the trap + * will not be executed until the command completes." + * + * Reality is that even if trap is not set, bash + * will not act on the signal until command completes. + * Try this. sleep5intoff.c: + * #include + * #include + * int main() { + * sigset_t set; + * sigemptyset(&set); + * sigaddset(&set, SIGINT); + * sigaddset(&set, SIGQUIT); + * sigprocmask(SIG_BLOCK, &set, NULL); + * sleep(5); + * return 0; + * } + * $ bash -c './sleep5intoff; echo hi' + * ^C^C^C^C <--- pressing ^C once a second + * $ _ + * $ bash -c './sleep5intoff; echo hi' + * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT) + * $ _ + */ + dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, jp); + if (!jp) + return exitstatus; st = getstatus(jp); #if JOBS @@ -5918,6 +6030,8 @@ static int substr_atoi(const char *s) #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 */ +#define EXP_KEEPNUL 0x200 /* do not skip NUL characters */ + /* * rmescape() flags */ @@ -5928,8 +6042,6 @@ static int substr_atoi(const char *s) /* Add CTLESC when necessary. */ #define QUOTES_ESC (EXP_FULL | EXP_CASE) -/* Do not skip NUL characters. */ -#define QUOTES_KEEPNUL EXP_TILDE /* * Structure specifying which parts of the string should be searched @@ -5958,26 +6070,6 @@ static struct ifsregion *ifslastp; /* holds expanded arg list */ static struct arglist exparg; -/* - * Our own itoa(). - * cvtnum() is used even if math support is off (to prepare $? values and such). - */ -static int -cvtnum(arith_t num) -{ - int len; - - /* 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; -} - /* * Break the argument string into pieces based upon IFS and add the * strings to the argument list. The regions of the string to be @@ -6235,43 +6327,63 @@ preglob(const char *pattern, int flag) /* * Put a string on the stack. */ -static void -memtodest(const char *p, size_t len, int syntax, int quotes) +static size_t +memtodest(const char *p, size_t len, int flags) { + int syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX; char *q; + char *s; if (!len) - return; + return 0; - q = makestrspace((quotes & QUOTES_ESC) ? len * 2 : len, expdest); + q = makestrspace(len * 2, expdest); + s = q; do { unsigned char c = *p++; if (c) { - if (quotes & QUOTES_ESC) { + if (flags & QUOTES_ESC) { int n = SIT(c, syntax); if (n == CCTL - || (syntax != BASESYNTAX && n == CBACK) + || ((flags & EXP_QUOTED) && n == CBACK) ) { USTPUTC(CTLESC, q); } } - } else if (!(quotes & QUOTES_KEEPNUL)) + } else if (!(flags & EXP_KEEPNUL)) continue; USTPUTC(c, q); } while (--len); expdest = q; + return q - s; } static size_t -strtodest(const char *p, int syntax, int quotes) +strtodest(const char *p, int flags) { size_t len = strlen(p); - memtodest(p, len, syntax, quotes); + memtodest(p, len, flags); return len; } +/* + * Our own itoa(). + * cvtnum() is used even if math support is off (to prepare $? values and such). + */ +static int +cvtnum(arith_t num, int flags) +{ + /* 32-bit and wider ints require buffer size of bytes*3 (or less) */ + /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127" */ + int len = (sizeof(arith_t) >= 4) ? sizeof(arith_t) * 3 : sizeof(arith_t) * 3 + 2; + char buf[len]; + + len = fmtstr(buf, len, ARITH_FMT, num); + return memtodest(buf, len, flags); +} + /* * Record the fact that we have to scan this region of the * string for IFS characters. @@ -6336,13 +6448,12 @@ removerecordregions(int endoff) } static char * -exptilde(char *startp, char *p, int flags) +exptilde(char *startp, char *p, int flag) { unsigned char c; char *name; struct passwd *pw; const char *home; - int quotes = flags & QUOTES_ESC; name = p + 1; @@ -6353,7 +6464,7 @@ exptilde(char *startp, char *p, int flags) case CTLQUOTEMARK: return startp; case ':': - if (flags & EXP_VARTILDE) + if (flag & EXP_VARTILDE) goto done; break; case '/': @@ -6371,10 +6482,10 @@ exptilde(char *startp, char *p, int flags) goto lose; home = pw->pw_dir; } - if (!home || !*home) + if (!home) goto lose; *p = c; - strtodest(home, SQSYNTAX, quotes); + strtodest(home, flag | EXP_QUOTED); return p; lose: *p = c; @@ -6474,7 +6585,6 @@ expbackq(union node *cmd, int flag) char *p; char *dest; int startloc; - int syntax = flag & EXP_QUOTED ? DQSYNTAX : BASESYNTAX; struct stackmark smark; INT_OFF; @@ -6488,7 +6598,7 @@ expbackq(union node *cmd, int flag) if (i == 0) goto read; for (;;) { - memtodest(p, i, syntax, flag & QUOTES_ESC); + memtodest(p, i, flag); read: if (in.fd < 0) break; @@ -6508,7 +6618,7 @@ expbackq(union node *cmd, int flag) /* Eat all trailing newlines */ dest = expdest; - for (; dest > (char *)stackblock() && dest[-1] == '\n';) + for (; dest > ((char *)stackblock() + startloc) && dest[-1] == '\n';) STUNPUTC(dest); expdest = dest; @@ -6572,7 +6682,7 @@ expari(int flag) if (flag & QUOTES_ESC) rmescapes(p + 1, 0, NULL); - len = cvtnum(ash_arith(p + 1)); + len = cvtnum(ash_arith(p + 1), flag); if (!(flag & EXP_QUOTED)) recordregion(begoff, begoff + len, 0); @@ -7197,11 +7307,10 @@ varvalue(char *name, int varflags, int flags, int quoted) int sep; int subtype = varflags & VSTYPE; int discard = subtype == VSPLUS || subtype == VSLENGTH; - int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; - int syntax; + flags |= EXP_KEEPNUL; + flags &= discard ? ~QUOTES_ESC : ~0; sep = (flags & EXP_FULL) << CHAR_BIT; - syntax = quoted ? DQSYNTAX : BASESYNTAX; switch (*name) { case '$': @@ -7218,7 +7327,7 @@ varvalue(char *name, int varflags, int flags, int quoted) if (num == 0) return -1; numvar: - len = cvtnum(num); + len = cvtnum(num, flags); goto check_1char_name; case '-': expdest = makestrspace(NOPTS, expdest); @@ -7267,11 +7376,11 @@ varvalue(char *name, int varflags, int flags, int quoted) if (!ap) return -1; while ((p = *ap++) != NULL) { - len += strtodest(p, syntax, quotes); + len += strtodest(p, flags); if (*ap && sep) { len++; - memtodest(&sepc, 1, syntax, quotes); + memtodest(&sepc, 1, flags); } } break; @@ -7298,7 +7407,7 @@ varvalue(char *name, int varflags, int flags, int quoted) if (!p) return -1; - len = strtodest(p, syntax, quotes); + len = strtodest(p, flags); #if ENABLE_UNICODE_SUPPORT if (subtype == VSLENGTH && len > 0) { reinit_unicode_for_ash(); @@ -7384,7 +7493,7 @@ evalvar(char *p, int flag) varunset(p, var, 0, 0); if (subtype == VSLENGTH) { - cvtnum(varlen > 0 ? varlen : 0); + cvtnum(varlen > 0 ? varlen : 0, flag); goto record; } @@ -7437,6 +7546,8 @@ evalvar(char *p, int flag) goto record; } + varlen = 0; + end: if (subtype != VSNORMAL) { /* skip to end of alternative */ int nesting = 1; @@ -7980,7 +8091,7 @@ struct cmdentry { #define DO_ABS 0x02 /* checks absolute paths */ #define DO_NOFUNC 0x04 /* don't return shell functions, for command */ #define DO_ALTPATH 0x08 /* using alternate path */ -#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ +#define DO_REGBLTIN 0x10 /* regular built-ins and functions only */ static void find_command(char *, struct cmdentry *, int, const char *); @@ -8039,6 +8150,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 @@ -8099,13 +8211,13 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) } else { try_PATH: e = ENOENT; - while ((cmdname = path_advance(&path, prog)) != NULL) { + while (padvance(&path, argv[0]) >= 0) { + cmdname = stackblock(); if (--idx < 0 && pathopt == NULL) { tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); if (errno != ENOENT && errno != ENOTDIR) e = errno; } - stunalloc(cmdname); } } @@ -8124,7 +8236,7 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", prog, e, suppress_int)); - ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found")); + ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, "not found")); /* NOTREACHED */ } @@ -8138,18 +8250,17 @@ printentry(struct tblentry *cmdp) idx = cmdp->param.index; path = pathval(); do { - name = path_advance(&path, cmdp->cmdname); - stunalloc(name); + padvance(&path, cmdp->cmdname); } while (--idx >= 0); + name = stackblock(); out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); } /* - * Clear out command entries. The argument specifies the first entry in - * PATH which has changed. + * Clear out command entries. */ static void -clearcmdentry(int firstchange) +clearcmdentry(void) { struct tblentry **tblp; struct tblentry **pp; @@ -8159,10 +8270,11 @@ clearcmdentry(int firstchange) for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { pp = tblp; while ((cmdp = *pp) != NULL) { - if ((cmdp->cmdtype == CMDNORMAL && - cmdp->param.index >= firstchange) - || (cmdp->cmdtype == CMDBUILTIN && - builtinloc >= firstchange) + if (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN + && !IS_BUILTIN_REGULAR(cmdp->param.cmd) + && builtinloc > 0 + ) ) { *pp = cmdp->next; free(cmdp); @@ -8262,7 +8374,7 @@ hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) char *name; if (nextopt("r") != '\0') { - clearcmdentry(0); + clearcmdentry(); return 0; } @@ -8281,7 +8393,11 @@ hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) cmdp = cmdlookup(name, 0); if (cmdp != NULL && (cmdp->cmdtype == CMDNORMAL - || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)) + || (cmdp->cmdtype == CMDBUILTIN + && !IS_BUILTIN_REGULAR(cmdp->param.cmd) + && builtinloc > 0 + ) + ) ) { delete_cmd_entry(); } @@ -8323,42 +8439,28 @@ hashcd(void) * Called with interrupts off. */ static void FAST_FUNC -changepath(const char *new) +changepath(const char *newval) { - const char *old; - int firstchange; + const char *new; int idx; - int idx_bltin; + int bltin; - old = pathval(); - firstchange = 9999; /* assume no change */ + new = newval; idx = 0; - idx_bltin = -1; + bltin = -1; for (;;) { - if (*old != *new) { - firstchange = idx; - if ((*old == '\0' && *new == ':') - || (*old == ':' && *new == '\0') - ) { - firstchange++; - } - old = new; /* ignore subsequent differences */ + if (*new == '%' && prefix(new + 1, "builtin")) { + bltin = idx; + break; } - if (*new == '\0') + new = strchr(new, ':'); + if (!new) break; - if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) - idx_bltin = idx; - if (*new == ':') - idx++; + idx++; new++; - old++; } - if (builtinloc < 0 && idx_bltin >= 0) - builtinloc = idx_bltin; /* zap builtins */ - if (builtinloc >= 0 && idx_bltin < 0) - firstchange = 0; - clearcmdentry(firstchange); - builtinloc = idx_bltin; + builtinloc = bltin; + clearcmdentry(); } enum { TEOF, @@ -8533,9 +8635,9 @@ describe_command(char *command, const char *path, int describe_command_verbose) p = command; } else { do { - p = path_advance(&path, command); - stunalloc(p); + padvance(&path, command); } while (--j >= 0); + p = stackblock(); } if (describe_command_verbose) { out1fmt(" is %s", p); @@ -8595,24 +8697,43 @@ typecmd(int argc UNUSED_PARAM, char **argv) } #if ENABLE_ASH_CMDCMD +static struct strlist * +fill_arglist(struct arglist *arglist, union node **argpp) +{ + struct strlist **lastp = arglist->lastp; + union node *argp; + + while ((argp = *argpp) != NULL) { + expandarg(argp, arglist, EXP_FULL | EXP_TILDE); + *argpp = argp->narg.next; + if (*lastp) + break; + } + + return *lastp; +} + /* Is it "command [-p] PROG ARGS" bltin, no other opts? Return ptr to "PROG" if yes */ -static char ** -parse_command_args(char **argv, const char **path) +static int +parse_command_args(struct arglist *arglist, union node **argpp, const char **path) { + struct strlist *sp = arglist->list; char *cp, c; for (;;) { - cp = *++argv; - if (!cp) - return NULL; + sp = sp->next ? sp->next : fill_arglist(arglist, argpp); + if (!sp) + return 0; + cp = sp->text; if (*cp++ != '-') break; c = *cp++; if (!c) break; if (c == '-' && !*cp) { - if (!*++argv) - return NULL; + if (!sp->next && !fill_arglist(arglist, argpp)) + return 0; + sp = sp->next; break; } do { @@ -8622,12 +8743,14 @@ parse_command_args(char **argv, const char **path) break; default: /* run 'typecmd' for other options */ - return NULL; + return 0; } c = *cp++; } while (c); } - return argv; + + arglist->list = sp; + return DO_NOFUNC; } static int FAST_FUNC @@ -8967,6 +9090,7 @@ defun(union node *func) #define SKIPBREAK (1 << 0) #define SKIPCONT (1 << 1) #define SKIPFUNC (1 << 2) +#define SKIPFUNCDEF (1 << 3) static smallint evalskip; /* set to SKIPxxx if we are skipping commands */ static int skipcount; /* number of levels to skip */ static int loopnest; /* current loop nesting level */ @@ -8987,12 +9111,17 @@ dotrap(void) { uint8_t *g; int sig; - uint8_t last_status; + int status, last_status; if (!pending_sig) return; - last_status = exitstatus; + status = savestatus; + last_status = status; + if (status < 0) { + status = exitstatus; + savestatus = status; + } pending_sig = 0; barrier(); @@ -9019,8 +9148,11 @@ dotrap(void) if (!p) continue; evalstring(p, 0); + if (evalskip != SKIPFUNC) + exitstatus = status; } - exitstatus = last_status; + + savestatus = last_status; TRACE(("dotrap returns\n")); } @@ -9044,8 +9176,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; @@ -9155,10 +9290,11 @@ evaltree(union node *n, int flags) dotrap(); if (checkexit & status) - raise_exception(EXEXIT); + raise_exception(EXEND); if (flags & EV_EXIT) - raise_exception(EXEXIT); + raise_exception(EXEND); + popstackmark(&smark); TRACE(("leaving evaltree (no interrupts)\n")); return exitstatus; } @@ -9219,14 +9355,12 @@ evalfor(union node *n, int flags) struct arglist arglist; union node *argp; struct strlist *sp; - struct stackmark smark; int status = 0; errlinno = lineno = n->ncase.linno; if (funcline) lineno -= funcline - 1; - setstackmark(&smark); arglist.list = NULL; arglist.lastp = &arglist.list; for (argp = n->nfor.args; argp; argp = argp->narg.next) { @@ -9243,7 +9377,6 @@ evalfor(union node *n, int flags) break; } loopnest--; - popstackmark(&smark); return status; } @@ -9254,14 +9387,12 @@ evalcase(union node *n, int flags) union node *cp; union node *patp; struct arglist arglist; - struct stackmark smark; int status = 0; errlinno = lineno = n->ncase.linno; if (funcline) lineno -= funcline - 1; - setstackmark(&smark); arglist.list = NULL; arglist.lastp = &arglist.list; expandarg(n->ncase.expr, &arglist, EXP_TILDE); @@ -9280,8 +9411,6 @@ evalcase(union node *n, int flags) } } out: - popstackmark(&smark); - return status; } @@ -9369,11 +9498,10 @@ expredir(union node *n) case NFROMFD: case NTOFD: /* >& */ if (redir->ndup.vname) { - expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); + expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR); if (fn.list == NULL) ash_msg_and_raise_error("redir error"); #if BASH_REDIR_OUTPUT -//FIXME: we used expandarg with different args! if (!isdigit_str9(fn.list->text)) { /* >&file, not >&fd */ if (redir->nfile.fd != 1) /* 123>&file - BAD */ @@ -9458,6 +9586,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. */ @@ -9472,8 +9605,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; @@ -9487,8 +9620,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 @@ -9500,10 +9641,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 @@ -9578,18 +9721,23 @@ poplocalvars(int keep) * Create a new localvar environment. */ static struct localvar_list * -pushlocalvars(void) +pushlocalvars(int push) { struct localvar_list *ll; + struct localvar_list *top; + + top = localvar_stack; + if (!push) + goto out; INT_OFF; ll = ckzalloc(sizeof(*ll)); /*ll->lv = NULL; - zalloc did it */ - ll->next = localvar_stack; + ll->next = top; localvar_stack = ll; INT_ON; - - return ll->next; + out: + return top; } static void @@ -9636,7 +9784,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) shellparam = saveparam; exception_handler = savehandler; INT_ON; - evalskip &= ~SKIPFUNC; + evalskip &= ~(SKIPFUNC | SKIPFUNCDEF); return e; } @@ -9648,7 +9796,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) * (options will be restored on return from the function). */ static void -mklocal(char *name) +mklocal(char *name, int flags) { struct localvar *lvp; struct var **vpp; @@ -9685,9 +9833,9 @@ mklocal(char *name) if (vp == NULL) { /* variable did not exist yet */ if (eq) - vp = setvareq(name, VSTRFIXED); + vp = setvareq(name, VSTRFIXED | flags); else - vp = setvar(name, NULL, VSTRFIXED); + vp = setvar(name, NULL, VSTRFIXED | flags); lvp->flags = VUNSET; } else { lvp->text = vp->var_text; @@ -9697,7 +9845,7 @@ mklocal(char *name) */ vp->flags |= VSTRFIXED|VTEXTFIXED; if (eq) - setvareq(name, 0); + setvareq(name, flags); else /* "local VAR" unsets VAR: */ setvar0(name, NULL); @@ -9723,7 +9871,7 @@ localcmd(int argc UNUSED_PARAM, char **argv) argv = argptr; while ((name = *argv++) != NULL) { - mklocal(name); + mklocal(name, 0); } return 0; } @@ -9781,12 +9929,23 @@ execcmd(int argc UNUSED_PARAM, char **argv) static int FAST_FUNC returncmd(int argc UNUSED_PARAM, char **argv) { + int skip; + int status; + /* * If called outside a function, do what ksh does; * skip the rest of the file. */ - evalskip = SKIPFUNC; - return argv[1] ? number(argv[1]) : exitstatus; + if (argv[1]) { + skip = SKIPFUNC; + status = number(argv[1]); + } else { + skip = SKIPFUNCDEF; + status = exitstatus; + } + evalskip = skip; + + return status; } /* Forward declarations for builtintab[] */ @@ -9873,7 +10032,7 @@ static const struct builtincmd builtintab[] = { #if ENABLE_ASH_GETOPTS { BUILTIN_REGULAR "getopts" , getoptscmd }, #endif - { BUILTIN_NOSPEC "hash" , hashcmd }, + { BUILTIN_REGULAR "hash" , hashcmd }, #if ENABLE_ASH_HELP { BUILTIN_NOSPEC "help" , helpcmd }, #endif @@ -9891,7 +10050,7 @@ static const struct builtincmd builtintab[] = { #if ENABLE_ASH_PRINTF { BUILTIN_REGULAR "printf" , printfcmd }, #endif - { BUILTIN_NOSPEC "pwd" , pwdcmd }, + { BUILTIN_REGULAR "pwd" , pwdcmd }, { BUILTIN_REGULAR "read" , readcmd }, { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, { BUILTIN_SPEC_REG "return" , returncmd }, @@ -9906,8 +10065,8 @@ static const struct builtincmd builtintab[] = { { BUILTIN_SPEC_REG "times" , timescmd }, { BUILTIN_SPEC_REG "trap" , trapcmd }, { BUILTIN_REGULAR "true" , truecmd }, - { BUILTIN_NOSPEC "type" , typecmd }, - { BUILTIN_NOSPEC "ulimit" , ulimitcmd }, + { BUILTIN_REGULAR "type" , typecmd }, + { BUILTIN_REGULAR "ulimit" , ulimitcmd }, { BUILTIN_REGULAR "umask" , umaskcmd }, #if ENABLE_ASH_ALIAS { BUILTIN_REGULAR "unalias" , unaliascmd }, @@ -9952,6 +10111,14 @@ 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. */ @@ -9975,26 +10142,29 @@ static int evalcommand(union node *cmd, int flags) { static const struct builtincmd null_bltin = { - "\0\0", bltincmd /* why three NULs? */ + BUILTIN_REGULAR "", bltincmd }; 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; char **argv; int argc; + struct strlist *osp; const struct strlist *sp; struct cmdentry cmdentry; struct job *jp; char *lastarg; const char *path; int spclbltin; + int cmd_flag; int status; char **nargv; smallint cmd_is_exec; + int vflags; + int vlocal; errlinno = lineno = cmd->ncmd.linno; if (funcline) @@ -10002,8 +10172,6 @@ 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(); file_stop = g_parsefile; back_exitstatus = 0; @@ -10014,28 +10182,58 @@ evalcommand(union node *cmd, int flags) arglist.lastp = &arglist.list; *arglist.lastp = NULL; + cmd_flag = 0; + cmd_is_exec = 0; + spclbltin = -1; + vflags = 0; + vlocal = 0; + path = NULL; + argc = 0; - if (cmd->ncmd.args) { - struct builtincmd *bcmd; - smallint pseudovarflag; + argp = cmd->ncmd.args; + osp = fill_arglist(&arglist, &argp); + if (osp) { + int pseudovarflag = 0; - bcmd = find_builtin(cmd->ncmd.args->narg.text); - pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + for (;;) { + find_command(arglist.list->text, &cmdentry, + cmd_flag | DO_REGBLTIN, pathval()); - for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { - struct strlist **spp; + vlocal++; - spp = arglist.lastp; - if (pseudovarflag && isassignment(argp->narg.text)) - expandarg(argp, &arglist, EXP_VARTILDE); - else - expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + /* implement bltin and command here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + + pseudovarflag = IS_BUILTIN_ASSIGN(cmdentry.u.cmd); + if (spclbltin < 0) { + spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); + vlocal = !spclbltin; + } + cmd_is_exec = cmdentry.u.cmd == EXECCMD; + if (cmdentry.u.cmd != COMMANDCMD) + break; - for (sp = *spp; sp; sp = sp->next) - argc++; + cmd_flag = parse_command_args(&arglist, &argp, &path); + if (!cmd_flag) + break; } + + for (; argp; argp = argp->narg.next) + expandarg(argp, &arglist, + pseudovarflag && + isassignment(argp->narg.text) ? + EXP_VARTILDE : EXP_FULL | EXP_TILDE); + + for (sp = arglist.list; sp; sp = sp->next) + argc++; + + if (cmd_is_exec && argc > 1) + vflags = VEXPORT; } + localvar_stop = pushlocalvars(vlocal); + /* Reserve one extra spot at the front for shellexec. */ nargv = stalloc(sizeof(char *) * (argc + 2)); argv = ++nargv; @@ -10063,23 +10261,27 @@ evalcommand(union node *cmd, int flags) } status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); - path = vpath.var_text; + if (status) { + bail: + exitstatus = status; + + /* We have a redirection error. */ + if (spclbltin > 0) + raise_exception(EXERROR); + + goto out; + } + for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { struct strlist **spp; - char *p; spp = varlist.lastp; expandarg(argp, &varlist, EXP_VARTILDE); - mklocal((*spp)->text); - - /* - * Modify the command lookup path, if a PATH= assignment - * is present - */ - p = (*spp)->text; - if (varcmp(p, path) == 0) - path = p; + if (vlocal) + mklocal((*spp)->text, VEXPORT); + else + setvareq((*spp)->text, vflags); } /* Print the command if xflag is set. */ @@ -10118,62 +10320,23 @@ evalcommand(union node *cmd, int flags) safe_write(preverrout_fd, "\n", 1); } - cmd_is_exec = 0; - spclbltin = -1; - /* Now locate the command. */ - if (argc) { - int cmd_flag = DO_ERR; -#if ENABLE_ASH_CMDCMD - const char *oldpath = path + 5; -#endif - path += 5; - for (;;) { - find_command(argv[0], &cmdentry, cmd_flag, path); - if (cmdentry.cmdtype == CMDUNKNOWN) { - flush_stdout_stderr(); - status = 127; - goto bail; - } - - /* implement bltin and command here */ - if (cmdentry.cmdtype != CMDBUILTIN) - break; - if (spclbltin < 0) - spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); - if (cmdentry.u.cmd == EXECCMD) - cmd_is_exec = 1; -#if ENABLE_ASH_CMDCMD - if (cmdentry.u.cmd == COMMANDCMD) { - path = oldpath; - nargv = parse_command_args(argv, &path); - if (!nargv) - break; - /* It's "command [-p] PROG ARGS" (that is, no -Vv). - * nargv => "PROG". path is updated if -p. - */ - argc -= nargv - argv; - argv = nargv; - cmd_flag |= DO_NOFUNC; - } else -#endif - break; - } + if (cmdentry.cmdtype != CMDBUILTIN + || !(IS_BUILTIN_REGULAR(cmdentry.u.cmd)) + ) { + path = path ? path : pathval(); + find_command(argv[0], &cmdentry, cmd_flag | DO_ERR, path); } - if (status) { - bail: - exitstatus = status; - - /* We have a redirection error. */ - if (spclbltin > 0) - raise_exception(EXERROR); - - goto out; - } + jp = NULL; /* Execute the command. */ switch (cmdentry.cmdtype) { + case CMDUNKNOWN: + status = 127; + flush_stdout_stderr(); + goto bail; + default: { #if ENABLE_FEATURE_SH_STANDALONE \ @@ -10200,7 +10363,7 @@ 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). */ - status = run_nofork_applet(applet_no, argv); + exitstatus = run_nofork_applet(applet_no, argv); environ = sv_environ; /* * Try enabling NOFORK for "yes" applet. @@ -10226,8 +10389,6 @@ evalcommand(union node *cmd, int flags) jp = makejob(/*cmd,*/ 1); if (forkshell(jp, cmd, FORK_FG) != 0) { /* parent */ - status = waitforjob(jp); - INT_ON; TRACE(("forked child exited with %d\n", status)); break; } @@ -10235,43 +10396,27 @@ evalcommand(union node *cmd, int flags) FORCE_INT_ON; /* fall through to exec'ing external program */ } - listsetvar(varlist.list, VEXPORT|VSTACK); shellexec(argv[0], argv, path, cmdentry.u.index); /* NOTREACHED */ } /* default */ case CMDBUILTIN: - 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 - * to reap the zombie and make kill detect that it's gone: */ - dowait(DOWAIT_NONBLOCK, NULL); - - if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { - if (exception_type == EXERROR && spclbltin <= 0) { - FORCE_INT_ON; - goto readstatus; - } + if (evalbltin(cmdentry.u.cmd, argc, argv, flags) + && !(exception_type == EXERROR && spclbltin <= 0) + ) { raise: longjmp(exception_handler->loc, 1); } - goto readstatus; + break; case CMDFUNCTION: - /* See above for the rationale */ - dowait(DOWAIT_NONBLOCK, NULL); if (evalfun(cmdentry.u.func, argc, argv, flags)) goto raise; - readstatus: - status = exitstatus; break; } /* switch */ + status = waitforjob(jp); + FORCE_INT_ON; + out: if (cmd->ncmd.redirect) popredir(/*drop:*/ cmd_is_exec); @@ -10285,7 +10430,6 @@ evalcommand(union node *cmd, int flags) */ setvar0("_", lastarg); } - popstackmark(&smark); return status; } @@ -10480,13 +10624,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 @@ -10746,6 +10888,12 @@ struct synstack { struct synstack *next; }; +static int +pgetc_top(struct synstack *stack) +{ + return stack->syntax == SQSYNTAX ? pgetc() : pgetc_eatbnl(); +} + static void synstack_push(struct synstack **stack, struct synstack *next, int syntax) { @@ -10923,8 +11071,12 @@ chkmail(void) mpath = mpathset() ? mpathval() : mailval(); new_hash = 0; for (;;) { - p = path_advance(&mpath, nullstr); - if (p == NULL) + int len; + + len = padvance_magic(&mpath, nullstr, 2); + if (!len) + break; + p = stackblock(); break; if (*p == '\0') continue; @@ -11025,6 +11177,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 { @@ -11039,7 +11193,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; } @@ -11047,14 +11201,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++; @@ -11065,7 +11222,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; @@ -11078,26 +11235,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); } @@ -11188,7 +11359,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) { @@ -11219,6 +11390,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) @@ -12066,7 +12263,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } USTPUTC(c, out); nlprompt(); - c = pgetc(); + c = pgetc_top(synstack); goto loop; /* continue outer loop */ case CWORD: USTPUTC(c, out); @@ -12098,8 +12295,6 @@ 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); @@ -12219,7 +12414,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) IF_ASH_ALIAS(if (c != PEOA)) USTPUTC(c, out); } - c = pgetc(); + c = pgetc_top(synstack); } /* for (;;) */ endword: @@ -12282,7 +12477,13 @@ checkend: { for (p = eofmark; STPUTC(c, out), *p; p++) { if (c != *p) goto more_heredoc; - + /* FIXME: fails for backslash-newlined terminator: + * cat <[}] */ int cc = c; @@ -12584,6 +12785,7 @@ parsebackq: { union node *n; char *str; size_t savelen; + struct heredoc *saveheredoclist; smallint saveprompt = 0; str = NULL; @@ -12659,6 +12861,9 @@ parsebackq: { *nlpp = stzalloc(sizeof(**nlpp)); /* (*nlpp)->next = NULL; - stzalloc did it */ + saveheredoclist = heredoclist; + heredoclist = NULL; + if (oldstyle) { saveprompt = doprompt; doprompt = 0; @@ -12668,21 +12873,22 @@ parsebackq: { if (oldstyle) doprompt = saveprompt; - else if (readtoken() != TRP) - raise_error_unexpected_syntax(TRP); + else { + if (readtoken() != TRP) + raise_error_unexpected_syntax(TRP); + setinputstring(nullstr); + } + + parseheredoc(); + heredoclist = saveheredoclist; (*nlpp)->n = n; - if (oldstyle) { - /* - * Start reading from old file again, ignoring any pushed back - * tokens left from the backquote parsing - */ - popfile(); + /* Start reading from old file again. */ + popfile(); + /* Ignore any pushed back tokens left from the backquote parsing. */ + if (oldstyle) tokpushback = 0; - } - while (stackblocksize() <= savelen) - growstackblock(); - STARTSTACKSTR(out); + out = growstackto(savelen + 1); if (str) { memcpy(out, str, savelen); STADJUST(savelen, out); @@ -12957,9 +13163,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 */ @@ -12976,41 +13185,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; + result = ps; + + 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) > ' */ - { - volatile int saveint; - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; - SAVE_INT(saveint); - if (setjmp(jmploc.loc) == 0) { - exception_handler = &jmploc; - readtoken1(pgetc(), syntax_type, FAKEEOFMARK, 0); - } - exception_handler = savehandler; - RESTORE_INT(saveint); - } - - doprompt = saveprompt; - - popfile(); + 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 @@ -13139,8 +13361,12 @@ cmdloop(int top) if (!top || numeof >= 50) break; if (!stoppedjobs()) { - if (!Iflag) + if (!Iflag) { + if (iflag) { + newline_and_flush(stderr); + } break; + } out2str("\nUse \"exit\" to leave shell.\n"); } numeof++; @@ -13158,7 +13384,7 @@ cmdloop(int top) skip = evalskip; if (skip) { - evalskip &= ~SKIPFUNC; + evalskip &= ~(SKIPFUNC | SKIPFUNCDEF); break; } } @@ -13170,33 +13396,32 @@ cmdloop(int top) * search for the file, which is necessary to find sub-commands. */ static char * -find_dot_file(char *name) +find_dot_file(char *basename) { char *fullname; const char *path = pathval(); struct stat statb; + int len; /* don't try this for absolute or relative paths */ - if (strchr(name, '/')) - return name; + if (strchr(basename, '/')) + return basename; - while ((fullname = path_advance(&path, name)) != NULL) { - if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { - /* - * Don't bother freeing here, since it will - * be freed by the caller. - */ - return fullname; + while ((len = padvance(&path, basename)) >= 0) { + fullname = stackblock(); + if ((!pathopt || *pathopt == 'f') + && !stat(fullname, &statb) && S_ISREG(statb.st_mode) + ) { + /* This will be freed by the caller. */ + return stalloc(len); } - if (fullname != name) - stunalloc(fullname); } /* not found in PATH */ #if ENABLE_ASH_BASH_SOURCE_CURDIR - return name; + return basename; #else - ash_msg_and_raise_error("%s: not found", name); + ash_msg_and_raise_error("%s: not found", basename); /* NOTREACHED */ #endif } @@ -13262,8 +13487,10 @@ exitcmd(int argc UNUSED_PARAM, char **argv) { if (stoppedjobs()) return 0; + if (argv[1]) - exitstatus = number(argv[1]); + savestatus = number(argv[1]); + raise_exception(EXEXIT); /* NOTREACHED */ } @@ -13297,6 +13524,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) int e; int updatetbl; struct builtincmd *bcmd; + int len; /* If name contains a slash, don't use PATH or hash table */ if (strchr(name, '/') != NULL) { @@ -13318,11 +13546,8 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) /* #if ENABLE_FEATURE_SH_STANDALONE... moved after builtin check */ updatetbl = (path == pathval()); - if (!updatetbl) { + if (!updatetbl) act |= DO_ALTPATH; - if (strstr(path, "%builtin") != NULL) - act |= DO_ALTBLTIN; - } /* If name is in the table, check answer will be ok */ cmdp = cmdlookup(name, 0); @@ -13335,16 +13560,19 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) abort(); #endif case CMDNORMAL: - bit = DO_ALTPATH; + bit = DO_ALTPATH | DO_REGBLTIN; break; case CMDFUNCTION: bit = DO_NOFUNC; break; case CMDBUILTIN: - bit = DO_ALTBLTIN; + bit = IS_BUILTIN_REGULAR(cmdp->param.cmd) ? 0 : DO_REGBLTIN; break; } if (act & bit) { + if (act & bit & DO_REGBLTIN) + goto fail; + updatetbl = 0; cmdp = NULL; } else if (cmdp->rehash == 0) @@ -13357,14 +13585,15 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) if (bcmd) { if (IS_BUILTIN_REGULAR(bcmd)) goto builtin_success; - if (act & DO_ALTPATH) { - if (!(act & DO_ALTBLTIN)) - goto builtin_success; - } else if (builtinloc <= 0) { + if (act & DO_ALTPATH) + goto builtin_success; + if (builtinloc <= 0) goto builtin_success; - } } + if (act & DO_REGBLTIN) + goto fail; + #if ENABLE_FEATURE_SH_STANDALONE { int applet_no = find_applet_by_name(name); @@ -13388,20 +13617,20 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) e = ENOENT; idx = -1; loop: - while ((fullname = path_advance(&path, name)) != NULL) { - stunalloc(fullname); - /* NB: code below will still use fullname - * despite it being "unallocated" */ + while ((len = padvance(&path, name)) >= 0) { + const char *lpathopt = pathopt; + + fullname = stackblock(); idx++; - if (pathopt) { - if (prefix(pathopt, "builtin")) { + if (lpathopt) { + if (*lpathopt == 'b') { if (bcmd) goto builtin_success; continue; - } - if ((act & DO_NOFUNC) - || !prefix(pathopt, "func") - ) { /* ignore unimplemented options */ + } else if (!(act & DO_NOFUNC)) { + /* handled below */ + } else { + /* ignore unimplemented options */ continue; } } @@ -13424,8 +13653,8 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) e = EACCES; /* if we fail, this will be the error */ if (!S_ISREG(statb.st_mode)) continue; - if (pathopt) { /* this is a %func directory */ - stalloc(strlen(fullname) + 1); + if (lpathopt) { /* this is a %func directory */ + stalloc(len); /* NB: stalloc will return space pointed by fullname * (because we don't have any intervening allocations * between stunalloc above and this stalloc) */ @@ -13468,6 +13697,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) #endif ash_msg("%s: %s", name, errmsg(e, "not found")); } + fail: entry->cmdtype = CMDUNKNOWN; return; @@ -13609,7 +13839,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 @@ -13914,6 +14145,47 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv) /* ============ main() and helpers */ +/* + * This routine is called when an error or an interrupt occurs in an + * interactive shell and control is returned to the main command loop + * but prior to exitshell. + */ +static void +exitreset(void) +{ + /* from eval.c: */ + if (savestatus >= 0) { + if (exception_type == EXEXIT || evalskip == SKIPFUNCDEF) + exitstatus = savestatus; + savestatus = -1; + } + evalskip = 0; + loopnest = 0; + + /* from expand.c: */ + ifsfree(); + + /* from redir.c: */ + unwindredir(NULL); +} + +/* + * This routine is called when an error or an interrupt occurs in an + * interactive shell and control is returned to the main command loop. + * (In dash, this function is auto-generated by build machinery). + */ +static void +reset(void) +{ + /* from input.c: */ + g_parsefile->left_in_buffer = 0; + g_parsefile->left_in_line = 0; /* clear input buffer */ + popallfiles(); + + /* from var.c: */ + unwindlocalvars(NULL); +} + /* * Called to exit the shell. */ @@ -13922,33 +14194,32 @@ exitshell(void) { struct jmploc loc; char *p; - 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)); - if (setjmp(loc.loc)) { - if (exception_type == EXEXIT) - status = exitstatus; + savestatus = exitstatus; + TRACE(("pid %d, exitshell(%d)\n", getpid(), savestatus)); + if (setjmp(loc.loc)) goto out; - } exception_handler = &loc; p = trap[0]; if (p) { trap[0] = NULL; evalskip = 0; evalstring(p, 0); + evalskip = SKIPFUNCDEF; /*free(p); - we'll exit soon */ } out: + exitreset(); /* dash wraps setjobctl(0) in "if (setjmp(loc.loc) == 0) {...}". * our setjobctl(0) does not panic if tcsetpgrp fails inside it. */ setjobctl(0); flush_stdout_stderr(); - _exit(status); + _exit(exitstatus); /* NOTREACHED */ } @@ -13963,11 +14234,6 @@ init(void) sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */ setsignal(SIGCHLD); - /* bash re-enables SIGHUP which is SIG_IGNed on entry. - * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$" - */ - signal(SIGHUP, SIG_DFL); - { char **envp; const char *p; @@ -14036,18 +14302,18 @@ procargs(char **argv) int login_sh; xargv = argv; + login_sh = xargv[0] && xargv[0][0] == '-'; #if NUM_SCRIPTS > 0 if (minusc) goto setarg0; #endif - login_sh = xargv[0] && xargv[0][0] == '-'; arg0 = xargv[0]; /* if (xargv[0]) - mmm, this is always true! */ xargv++; argptr = xargv; for (i = 0; i < NOPTS; i++) optlist[i] = 2; - if (options(/*cmdline:*/ 1, &login_sh)) { + if (options(&login_sh)) { /* it already printed err message */ raise_exception(EXERROR); } @@ -14058,8 +14324,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++) @@ -14108,33 +14379,6 @@ read_profile(const char *name) popfile(); } -/* - * This routine is called when an error or an interrupt occurs in an - * interactive shell and control is returned to the main command loop. - * (In dash, this function is auto-generated by build machinery). - */ -static void -reset(void) -{ - /* from eval.c: */ - evalskip = 0; - loopnest = 0; - - /* from expand.c: */ - ifsfree(); - - /* from input.c: */ - g_parsefile->left_in_buffer = 0; - g_parsefile->left_in_line = 0; /* clear input buffer */ - popallfiles(); - - /* from redir.c: */ - unwindredir(NULL); - - /* from var.c: */ - unwindlocalvars(NULL); -} - #if PROFILE static short profile_buf[16384]; extern int etext(); @@ -14148,7 +14392,11 @@ 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; @@ -14169,21 +14417,21 @@ 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; smallint s; - reset(); + exitreset(); e = exception_type; s = state; - if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) { + if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl) { exitshell(); } + + reset(); + if (e == EXINT) { newline_and_flush(stderr); } @@ -14246,10 +14494,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) { @@ -14276,6 +14531,14 @@ int ash_main(int argc UNUSED_PARAM, char **argv) } #endif state4: /* XXX ??? - why isn't this before the "if" statement */ + + /* Interactive bash re-enables SIGHUP which is SIG_IGNed on entry. + * Try: + * trap '' hup; bash; echo RET # type "kill -hup $$", see SIGHUP having effect + * trap '' hup; bash -c 'kill -hup $$; echo ALIVE' # here SIGHUP is SIG_IGNed + */ + signal(SIGHUP, SIG_DFL); + cmdloop(1); } #if PROFILE