X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fash.c;h=481b84138dbed7da9286ca525934f34097d3fa08;hb=4b8b37f9815892a1c221c0ca9f8eec623ab71866;hp=6de71f6d7634cfcf5477bbde31baecd7b999df5c;hpb=6514c5e35c31624d5e92400a7ce261c9e93774fe;p=oweals%2Fbusybox.git diff --git a/shell/ash.c b/shell/ash.c index 6de71f6d7..481b84138 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -8,7 +8,6 @@ * Copyright (c) 1997-2005 Herbert Xu * was re-ported from NetBSD and debianized. * - * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * @@ -18,21 +17,7 @@ */ /* - * rewrite arith.y to micro stack based cryptic algorithm by - * Copyright (c) 2001 Aaron Lehmann - * - * Modified by Paul Mundt (c) 2004 to support - * dynamic variables. - * - * Modified by Vladimir Oleynik (c) 2001-2005 to be - * used in busybox and size optimizations, - * rewrote arith (see notes to this), added locale support, - * rewrote dynamic variables. - * - */ - -/* - * The follow should be set to reflect the type of system you have: + * The following should be set to reflect the type of system you have: * JOBS -> 1 if you have Berkeley job control, 0 otherwise. * define SYSV if you are running under System V. * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) @@ -42,6 +27,11 @@ * a quit signal will generate a core dump. */ #define DEBUG 0 +/* Tweak debug output verbosity here */ +#define DEBUG_TIME 0 +#define DEBUG_PID 1 +#define DEBUG_SIG 1 + #define PROFILE 0 #define IFS_BROKEN @@ -49,21 +39,41 @@ #define JOBS ENABLE_ASH_JOB_CONTROL #if DEBUG -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif #endif #include "busybox.h" /* for applet_names */ +//TODO: pull in some .h and find out do we have SINGLE_APPLET_MAIN? +//#include "applet_tables.h" doesn't work #include #include #include -#if JOBS || ENABLE_ASH_READ_NCHARS -#include +#include "math.h" +#if ENABLE_ASH_RANDOM_SUPPORT +# include "random.h" +#else +# define CLEAR_RANDOM_T(rnd) ((void)0) +#endif + +#if defined SINGLE_APPLET_MAIN +/* STANDALONE does not make sense, and won't compile */ +# undef CONFIG_FEATURE_SH_STANDALONE +# undef ENABLE_FEATURE_SH_STANDALONE +# undef IF_FEATURE_SH_STANDALONE +# undef IF_NOT_FEATURE_SH_STANDALONE(...) +# define ENABLE_FEATURE_SH_STANDALONE 0 +# define IF_FEATURE_SH_STANDALONE(...) +# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__ +#endif + +#ifndef PIPE_BUF +# define PIPE_BUF 4096 /* amount of buffering in a pipe */ #endif #if defined(__uClinux__) -#error "Do not even bother, ash will not run on uClinux" +# error "Do not even bother, ash will not run on NOMMU machine" #endif @@ -74,14 +84,6 @@ #define CMDTABLESIZE 31 /* should be prime */ -/* ============ Misc helpers */ - -#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0) - -/* C99 say: "char" declaration may be signed or unsigned default */ -#define signed_char2int(sc) ((int)((signed char)sc)) - - /* ============ Shell options */ static const char *const optletters_optnames[] = { @@ -115,12 +117,12 @@ enum { NOPTS = ARRAY_SIZE(optletters_optnames) }; static const char homestr[] ALIGN1 = "HOME"; static const char snlfmt[] ALIGN1 = "%s\n"; -static const char illnum[] ALIGN1 = "Illegal number: %s"; +static const char msg_illnum[] ALIGN1 = "Illegal number: %s"; /* * We enclose jmp_buf in a structure so that we can declare pointers to * jump locations. The global variable handler contains the location to - * jump to when an exception occurs, and the global variable exception + * jump to when an exception occurs, and the global variable exception_type * contains a code identifying the exception. To implement nested * exception handlers, the user should save the value of handler on entry * to an inner scope, set handler to point to a jmploc structure for the @@ -145,15 +147,11 @@ struct globals_misc { struct jmploc *exception_handler; -// disabled by vda: cannot understand how it was supposed to work - -// cannot fix bugs. That's why you have to explain your non-trivial designs! -// /* do we generate EXSIG events */ -// int exsig; /* counter */ - volatile int suppressint; /* counter */ - volatile /*sig_atomic_t*/ smallint intpending; /* 1 = got SIGINT */ + volatile int suppress_int; /* counter */ + volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ /* last pending signal */ - volatile /*sig_atomic_t*/ smallint pendingsig; - smallint exception; /* kind of exception (0..5) */ + volatile /*sig_atomic_t*/ smallint pending_sig; + smallint exception_type; /* kind of exception (0..5) */ /* exceptions */ #define EXINT 0 /* SIGINT received */ #define EXERROR 1 /* a generic error */ @@ -189,24 +187,22 @@ struct globals_misc { /* * Sigmode records the current value of the signal handlers for the various * modes. A value of zero means that the current handler is not known. - * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + * S_HARD_IGN indicates that the signal was ignored on entry to the shell. */ char sigmode[NSIG - 1]; -#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_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 permenantly */ -#define S_RESET 5 /* temporary - to reset a hard ignored sig */ /* indicates specified signal received */ - char gotsig[NSIG - 1]; + uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ char *trap[NSIG]; + char **trap_ptr; /* used only by "trap hack" */ /* Rarely referenced stuff */ #if ENABLE_ASH_RANDOM_SUPPORT - /* Random number generators */ - int32_t random_galois_LFSR; /* Galois LFSR (fast but weak). signed! */ - uint32_t random_LCG; /* LCG (fast but weak) */ + random_t random_gen; #endif pid_t backgndpid; /* pid of last background process */ smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */ @@ -220,19 +216,18 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; #define physdir (G_misc.physdir ) #define arg0 (G_misc.arg0 ) #define exception_handler (G_misc.exception_handler) -#define exception (G_misc.exception ) -#define suppressint (G_misc.suppressint ) -#define intpending (G_misc.intpending ) -//#define exsig (G_misc.exsig ) -#define pendingsig (G_misc.pendingsig ) +#define exception_type (G_misc.exception_type ) +#define suppress_int (G_misc.suppress_int ) +#define pending_int (G_misc.pending_int ) +#define pending_sig (G_misc.pending_sig ) #define isloginsh (G_misc.isloginsh ) #define nullstr (G_misc.nullstr ) #define optlist (G_misc.optlist ) #define sigmode (G_misc.sigmode ) #define gotsig (G_misc.gotsig ) #define trap (G_misc.trap ) -#define random_galois_LFSR (G_misc.random_galois_LFSR) -#define random_LCG (G_misc.random_LCG ) +#define trap_ptr (G_misc.trap_ptr ) +#define random_gen (G_misc.random_gen ) #define backgndpid (G_misc.backgndpid ) #define job_warning (G_misc.job_warning) #define INIT_G_misc() do { \ @@ -240,11 +235,44 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; barrier(); \ curdir = nullstr; \ physdir = nullstr; \ + trap_ptr = trap; \ } while (0) -/* ============ Interrupts / exceptions */ +/* ============ DEBUG */ +#if DEBUG +static void trace_printf(const char *fmt, ...); +static void trace_vprintf(const char *fmt, va_list va); +# define TRACE(param) trace_printf param +# define TRACEV(param) trace_vprintf param +# define close(fd) do { \ + int dfd = (fd); \ + if (close(dfd) < 0) \ + bb_error_msg("bug on %d: closing %d(0x%x)", \ + __LINE__, dfd, dfd); \ +} while (0) +#else +# define TRACE(param) +# define TRACEV(param) +#endif + + +/* ============ Utility functions */ +#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0) + +/* C99 says: "char" declaration may be signed or unsigned by default */ +#define signed_char2int(sc) ((int)(signed char)(sc)) + +static int isdigit_str9(const char *str) +{ + int maxlen = 9 + 1; /* max 9 digits: 999999999 */ + while (--maxlen && isdigit(*str)) + str++; + return (*str == '\0'); +} + +/* ============ Interrupts / exceptions */ /* * These macros allow the user to suspend the handling of interrupt signals * over a period of time. This is similar to SIGHOLD or to sigblock, but @@ -252,14 +280,14 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; * more fun than worrying about efficiency and portability. :-)) */ #define INT_OFF do { \ - suppressint++; \ + suppress_int++; \ xbarrier(); \ } while (0) /* * Called to raise an exception. Since C doesn't include exceptions, we * just do a longjmp to the exception handler. The type of exception is - * stored in the global variable "exception". + * stored in the global variable "exception_type". */ static void raise_exception(int) NORETURN; static void @@ -270,9 +298,15 @@ raise_exception(int e) abort(); #endif INT_OFF; - exception = e; + exception_type = e; longjmp(exception_handler->loc, 1); } +#if DEBUG +#define raise_exception(e) do { \ + TRACE(("raising exception %d on line %d\n", (e), __LINE__)); \ + raise_exception(e); \ +} while (0) +#endif /* * Called from trap.c when a SIGINT is received. (If the user specifies @@ -285,97 +319,61 @@ static void raise_interrupt(void) NORETURN; static void raise_interrupt(void) { - int i; + int ex_type; - intpending = 0; + pending_int = 0; /* Signal is not automatically unmasked after it is raised, * do it ourself - unmask all signals */ sigprocmask_allsigs(SIG_UNBLOCK); - /* pendingsig = 0; - now done in onsig() */ + /* pending_sig = 0; - now done in onsig() */ - i = EXSIG; + ex_type = EXSIG; if (gotsig[SIGINT - 1] && !trap[SIGINT]) { if (!(rootshell && iflag)) { /* Kill ourself with SIGINT */ signal(SIGINT, SIG_DFL); raise(SIGINT); } - i = EXINT; + ex_type = EXINT; } - raise_exception(i); + raise_exception(ex_type); /* NOTREACHED */ } +#if DEBUG +#define raise_interrupt() do { \ + TRACE(("raising interrupt on line %d\n", __LINE__)); \ + raise_interrupt(); \ +} while (0) +#endif -#if ENABLE_ASH_OPTIMIZE_FOR_SIZE -static void +static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void int_on(void) { - if (--suppressint == 0 && intpending) { + xbarrier(); + if (--suppress_int == 0 && pending_int) { raise_interrupt(); } } #define INT_ON int_on() -static void +static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void force_int_on(void) { - suppressint = 0; - if (intpending) + xbarrier(); + suppress_int = 0; + if (pending_int) raise_interrupt(); } #define FORCE_INT_ON force_int_on() -#else -#define INT_ON do { \ - xbarrier(); \ - if (--suppressint == 0 && intpending) \ - raise_interrupt(); \ -} while (0) -#define FORCE_INT_ON do { \ - xbarrier(); \ - suppressint = 0; \ - if (intpending) \ - raise_interrupt(); \ -} while (0) -#endif /* ASH_OPTIMIZE_FOR_SIZE */ -#define SAVE_INT(v) ((v) = suppressint) +#define SAVE_INT(v) ((v) = suppress_int) #define RESTORE_INT(v) do { \ xbarrier(); \ - suppressint = (v); \ - if (suppressint == 0 && intpending) \ + suppress_int = (v); \ + if (suppress_int == 0 && pending_int) \ raise_interrupt(); \ } while (0) -/* - * Ignore a signal. Only one usage site - in forkchild() - */ -static void -ignoresig(int signo) -{ - if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { - signal(signo, SIG_IGN); - } - sigmode[signo - 1] = S_HARD_IGN; -} - -/* - * Signal handler. Only one usage site - in setsignal() - */ -static void -onsig(int signo) -{ - gotsig[signo - 1] = 1; - pendingsig = signo; - - if (/* exsig || */ (signo == SIGINT && !trap[SIGINT])) { - if (!suppressint) { - pendingsig = 0; - raise_interrupt(); /* does not return */ - } - intpending = 1; - } -} - /* ============ Stdout/stderr output */ @@ -460,15 +458,15 @@ out2str(const char *p) /* ============ Parser structures */ /* control characters in argument strings */ -#define CTLESC '\201' /* escape next character */ -#define CTLVAR '\202' /* variable defn */ -#define CTLENDVAR '\203' -#define CTLBACKQ '\204' +#define CTLESC ((unsigned char)'\201') /* escape next character */ +#define CTLVAR ((unsigned char)'\202') /* variable defn */ +#define CTLENDVAR ((unsigned char)'\203') +#define CTLBACKQ ((unsigned char)'\204') #define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ /* CTLBACKQ | CTLQUOTE == '\205' */ -#define CTLARI '\206' /* arithmetic expression */ -#define CTLENDARI '\207' -#define CTLQUOTEMARK '\210' +#define CTLARI ((unsigned char)'\206') /* arithmetic expression */ +#define CTLENDARI ((unsigned char)'\207') +#define CTLQUOTEMARK ((unsigned char)'\210') /* variable substitution byte (follows CTLVAR) */ #define VSTYPE 0x0f /* type of variable substitution */ @@ -496,32 +494,36 @@ static const char dolatstr[] ALIGN1 = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; -#define NCMD 0 -#define NPIPE 1 -#define NREDIR 2 -#define NBACKGND 3 +#define NCMD 0 +#define NPIPE 1 +#define NREDIR 2 +#define NBACKGND 3 #define NSUBSHELL 4 -#define NAND 5 -#define NOR 6 -#define NSEMI 7 -#define NIF 8 -#define NWHILE 9 -#define NUNTIL 10 -#define NFOR 11 -#define NCASE 12 -#define NCLIST 13 -#define NDEFUN 14 -#define NARG 15 -#define NTO 16 -#define NCLOBBER 17 -#define NFROM 18 -#define NFROMTO 19 -#define NAPPEND 20 -#define NTOFD 21 -#define NFROMFD 22 -#define NHERE 23 -#define NXHERE 24 -#define NNOT 25 +#define NAND 5 +#define NOR 6 +#define NSEMI 7 +#define NIF 8 +#define NWHILE 9 +#define NUNTIL 10 +#define NFOR 11 +#define NCASE 12 +#define NCLIST 13 +#define NDEFUN 14 +#define NARG 15 +#define NTO 16 +#if ENABLE_ASH_BASH_COMPAT +#define NTO2 17 +#endif +#define NCLOBBER 18 +#define NFROM 19 +#define NFROMTO 20 +#define NAPPEND 21 +#define NTOFD 22 +#define NFROMFD 23 +#define NHERE 24 +#define NXHERE 25 +#define NNOT 26 +#define N_NUMBER 27 union node; @@ -584,20 +586,26 @@ struct narg { struct nodelist *backquote; }; +/* nfile and ndup layout must match! + * NTOFD (>&fdnum) uses ndup structure, but we may discover mid-flight + * that it is actually NTO2 (>&file), and change its type. + */ struct nfile { smallint type; union node *next; int fd; + int _unused_dupfd; union node *fname; char *expfname; }; struct ndup { smallint type; - union node *next; /* must match nfile's layout */ - int fd; /* must match nfile's layout */ + union node *next; + int fd; int dupfd; union node *vname; + char *_unused_expfname; }; struct nhere { @@ -629,6 +637,12 @@ union node { struct nnot nnot; }; +/* + * NODE_EOF is returned by parsecmd when it encounters an end of file. + * It must be distinct from NULL. + */ +#define NODE_EOF ((union node *) -1L) + struct nodelist { struct nodelist *next; union node *n; @@ -663,6 +677,12 @@ trace_printf(const char *fmt, ...) if (debug != 1) return; + if (DEBUG_TIME) + fprintf(tracefile, "%u ", (int) time(NULL)); + if (DEBUG_PID) + fprintf(tracefile, "[%u] ", (int) getpid()); + if (DEBUG_SIG) + fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int); va_start(va, fmt); vfprintf(tracefile, fmt, va); va_end(va); @@ -673,6 +693,12 @@ trace_vprintf(const char *fmt, va_list va) { if (debug != 1) return; + if (DEBUG_TIME) + fprintf(tracefile, "%u ", (int) time(NULL)); + if (DEBUG_PID) + fprintf(tracefile, "[%u] ", (int) getpid()); + if (DEBUG_SIG) + fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int); vfprintf(tracefile, fmt, va); } @@ -900,8 +926,11 @@ shcmd(union node *cmd, FILE *fp) case NTO: s = ">>"+1; dftfd = 1; break; case NCLOBBER: s = ">|"; dftfd = 1; break; case NAPPEND: s = ">>"; dftfd = 1; break; +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif case NTOFD: s = ">&"; dftfd = 1; break; - case NFROM: s = "<"; break; + case NFROM: s = "<"; break; case NFROMFD: s = "<&"; break; case NFROMTO: s = "<>"; break; default: s = "*error*"; break; @@ -928,6 +957,12 @@ shtree(union node *n, int ind, char *pfx, FILE *fp) return; indent(ind, pfx, fp); + + if (n == NODE_EOF) { + fputs("", fp); + return; + } + switch (n->type) { case NSEMI: s = "; "; @@ -950,7 +985,7 @@ shtree(union node *n, int ind, char *pfx, FILE *fp) break; case NPIPE: for (lp = n->npipe.cmdlist; lp; lp = lp->next) { - shcmd(lp->n, fp); + shtree(lp->n, 0, NULL, fp); if (lp->next) fputs(" | ", fp); } @@ -971,17 +1006,9 @@ static void showtree(union node *n) { trace_puts("showtree called\n"); - shtree(n, 1, NULL, stdout); + shtree(n, 1, NULL, stderr); } -#define TRACE(param) trace_printf param -#define TRACEV(param) trace_vprintf param - -#else - -#define TRACE(param) -#define TRACEV(param) - #endif /* DEBUG */ @@ -999,8 +1026,8 @@ struct alias; struct strpush { struct strpush *prev; /* preceding string on stack */ - char *prevstring; - int prevnleft; + char *prev_string; + int prev_left_in_line; #if ENABLE_ASH_ALIAS struct alias *ap; /* if push was associated with an alias */ #endif @@ -1011,9 +1038,9 @@ struct parsefile { struct parsefile *prev; /* preceding file on stack */ int linno; /* current line */ int fd; /* file descriptor (or -1 if string) */ - int nleft; /* number of chars left in this line */ - int lleft; /* number of chars left in this buffer */ - char *nextc; /* next char in buffer */ + int left_in_line; /* number of chars left in this line */ + int left_in_buffer; /* number of chars left in this buffer past the line */ + char *next_to_pgetc; /* next char in buffer */ char *buf; /* input buffer */ struct strpush *strpush; /* for pushing strings at this level */ struct strpush basestrpush; /* so pushing one is fast */ @@ -1080,6 +1107,14 @@ ash_msg_and_raise_error(const char *msg, ...) va_end(ap); } +static void raise_error_syntax(const char *) NORETURN; +static void +raise_error_syntax(const char *msg) +{ + ash_msg_and_raise_error("syntax error: %s", msg); + /* NOTREACHED */ +} + static void ash_msg_and_raise(int, const char *, ...) NORETURN; static void ash_msg_and_raise(int cond, const char *msg, ...) @@ -1122,6 +1157,49 @@ errmsg(int e, const char *em) /* ============ Memory allocation */ +#if 0 +/* I consider these wrappers nearly useless: + * ok, they return you to nearest exception handler, but + * how much memory do you leak in the process, making + * memory starvation worse? + */ +static void * +ckrealloc(void * p, size_t nbytes) +{ + p = realloc(p, nbytes); + if (!p) + ash_msg_and_raise_error(bb_msg_memory_exhausted); + return p; +} + +static void * +ckmalloc(size_t nbytes) +{ + return ckrealloc(NULL, nbytes); +} + +static void * +ckzalloc(size_t nbytes) +{ + return memset(ckmalloc(nbytes), 0, nbytes); +} + +static char * +ckstrdup(const char *s) +{ + char *p = strdup(s); + if (!p) + ash_msg_and_raise_error(bb_msg_memory_exhausted); + return p; +} +#else +/* Using bbox equivalents. They exit if out of memory */ +# define ckrealloc xrealloc +# define ckmalloc xmalloc +# define ckzalloc xzalloc +# define ckstrdup xstrdup +#endif + /* * It appears that grabstackstr() will barf with such alignments * because stalloc() will return a string allocated in a new stackblock. @@ -1131,7 +1209,7 @@ enum { /* Most machines require the value returned from malloc to be aligned * in some way. The following macro will get this right * on many machines. */ - SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1, + SHELL_SIZE = sizeof(union { int i; char *cp; double d; }) - 1, /* Minimum size of a block */ MINSIZE = SHELL_ALIGN(504), }; @@ -1177,43 +1255,10 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack; herefd = -1; \ } while (0) + #define stackblock() ((void *)g_stacknxt) #define stackblocksize() g_stacknleft - -static void * -ckrealloc(void * p, size_t nbytes) -{ - p = realloc(p, nbytes); - if (!p) - ash_msg_and_raise_error(bb_msg_memory_exhausted); - return p; -} - -static void * -ckmalloc(size_t nbytes) -{ - return ckrealloc(NULL, nbytes); -} - -static void * -ckzalloc(size_t nbytes) -{ - return memset(ckmalloc(nbytes), 0, nbytes); -} - -/* - * Make a copy of a string in safe storage. - */ -static char * -ckstrdup(const char *s) -{ - char *p = strdup(s); - if (!p) - ash_msg_and_raise_error(bb_msg_memory_exhausted); - return p; -} - /* * Parse trees for commands are allocated in lifo order, so we use a stack * to make this more efficient, and also to avoid all sorts of exception @@ -1518,7 +1563,7 @@ static int number(const char *s) { if (!is_number(s)) - ash_msg_and_raise_error(illnum, s); + ash_msg_and_raise_error(msg_illnum, s); return atoi(s); } @@ -1660,14 +1705,14 @@ freeparam(volatile struct shparam *param) } #if ENABLE_ASH_GETOPTS -static void getoptsreset(const char *value); +static void FAST_FUNC getoptsreset(const char *value); #endif struct var { struct var *next; /* next entry in hash list */ int flags; /* flags are defined above */ const char *text; /* name=value */ - void (*func)(const char *); /* function to be called when */ + void (*func)(const char *) FAST_FUNC; /* function to be called when */ /* the variable gets set/unset */ }; @@ -1704,13 +1749,13 @@ static const char defifs[] ALIGN1 = " \t\n"; /* Need to be before varinit_data[] */ #if ENABLE_LOCALE_SUPPORT -static void +static void FAST_FUNC change_lc_all(const char *value) { if (value && *value != '\0') setlocale(LC_ALL, value); } -static void +static void FAST_FUNC change_lc_ctype(const char *value) { if (value && *value != '\0') @@ -1719,17 +1764,17 @@ change_lc_ctype(const char *value) #endif #if ENABLE_ASH_MAIL static void chkmail(void); -static void changemail(const char *); +static void changemail(const char *) FAST_FUNC; #endif -static void changepath(const char *); +static void changepath(const char *) FAST_FUNC; #if ENABLE_ASH_RANDOM_SUPPORT -static void change_random(const char *); +static void change_random(const char *) FAST_FUNC; #endif static const struct { int flags; const char *text; - void (*func)(const char *); + void (*func)(const char *) FAST_FUNC; } varinit_data[] = { #ifdef IFS_BROKEN { VSTRFIXED|VTEXTFIXED , defifsvar , NULL }, @@ -1835,7 +1880,7 @@ extern struct globals_var *const ash_ptr_to_globals_var; #define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) #if ENABLE_ASH_GETOPTS -static void +static void FAST_FUNC getoptsreset(const char *value) { shellparam.optind = number(value); @@ -1954,7 +1999,7 @@ findvar(struct var **vpp, const char *name) /* * Find the value of a variable. Returns NULL if not set. */ -static char * +static const char * lookupvar(const char *name) { struct var *v; @@ -1980,7 +2025,7 @@ lookupvar(const char *name) /* * Search the environment of a builtin command. */ -static char * +static const char * bltinlookup(const char *name) { struct strlist *sp; @@ -2198,17 +2243,17 @@ listvars(int on, int off, char ***end) /* ============ Path search helper * * The variable path (passed by reference) should be set to the start - * of the path before the first call; padvance will update - * this value as it proceeds. Successive calls to padvance will return + * of the path before the first call; path_advance will update + * this value as it proceeds. Successive calls to path_advance will return * the possible path expansions in sequence. If an option (indicated by * a percent sign) appears in the path entry then the global variable * pathopt will be set to point to it; otherwise pathopt will be set to * NULL. */ -static const char *pathopt; /* set by padvance */ +static const char *pathopt; /* set by path_advance */ static char * -padvance(const char **path, const char *name) +path_advance(const char **path, const char *name) { const char *p; char *q; @@ -2313,8 +2358,6 @@ setprompt(int whichprompt) #define CD_PHYSICAL 1 #define CD_PRINT 2 -static int docd(const char *, int); - static int cdopt(void) { @@ -2322,7 +2365,7 @@ cdopt(void) int i, j; j = 'L'; - while ((i = nextopt("LP"))) { + while ((i = nextopt("LP")) != '\0') { if (i != j) { flags ^= CD_PHYSICAL; j = i; @@ -2466,7 +2509,7 @@ docd(const char *dest, int flags) return err; } -static int +static int FAST_FUNC cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { const char *dest; @@ -2512,7 +2555,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) } do { c = *path; - p = padvance(&path, dest); + p = path_advance(&path, dest); if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { if (c && c != ':') flags |= CD_PRINT; @@ -2530,7 +2573,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return 0; } -static int +static int FAST_FUNC pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { int flags; @@ -2549,34 +2592,36 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* ============ ... */ + #define IBUFSIZ COMMON_BUFSIZE -#define basebuf bb_common_bufsiz1 /* buffer for top level input file */ +/* buffer for top level input file */ +#define basebuf bb_common_bufsiz1 /* Syntax classes */ -#define CWORD 0 /* character is nothing special */ -#define CNL 1 /* newline character */ -#define CBACK 2 /* a backslash character */ -#define CSQUOTE 3 /* single quote */ -#define CDQUOTE 4 /* double quote */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ #define CENDQUOTE 5 /* a terminating quote */ -#define CBQUOTE 6 /* backwards single quote */ -#define CVAR 7 /* a dollar sign */ -#define CENDVAR 8 /* a '}' character */ -#define CLP 9 /* a left paren in arithmetic */ -#define CRP 10 /* a right paren in arithmetic */ +#define CBQUOTE 6 /* backwards single quote */ +#define CVAR 7 /* a dollar sign */ +#define CENDVAR 8 /* a '}' character */ +#define CLP 9 /* a left paren in arithmetic */ +#define CRP 10 /* a right paren in arithmetic */ #define CENDFILE 11 /* end of file */ -#define CCTL 12 /* like CWORD, except it must be escaped */ -#define CSPCL 13 /* these terminate a word */ -#define CIGN 14 /* character should be ignored */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSPCL 13 /* these terminate a word */ +#define CIGN 14 /* character should be ignored */ #if ENABLE_ASH_ALIAS -#define SYNBASE 130 -#define PEOF -130 -#define PEOA -129 +#define SYNBASE 130 +#define PEOF -130 +#define PEOA -129 #define PEOA_OR_PEOF PEOA #else -#define SYNBASE 129 -#define PEOF -129 +#define SYNBASE 129 +#define PEOF -129 #define PEOA_OR_PEOF PEOF #endif @@ -2591,7 +2636,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #define USE_SIT_FUNCTION #endif -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT static const char S_I_T[][4] = { #if ENABLE_ASH_ALIAS { CSPCL, CIGN, CIGN, CIGN }, /* 0, PEOA */ @@ -2635,7 +2680,7 @@ static const char S_I_T[][3] = { { CCTL, CCTL, CCTL } /* 14, CTLESC ... */ #endif }; -#endif /* ASH_MATH_SUPPORT */ +#endif /* SH_MATH_SUPPORT */ #ifdef USE_SIT_FUNCTION @@ -2661,23 +2706,24 @@ SIT(int c, int syntax) const char *s; int indx; - if (c == PEOF) /* 2^8+2 */ + if (c == PEOF) { /* 2^8+2 */ return CENDFILE; + } #if ENABLE_ASH_ALIAS - if (c == PEOA) /* 2^8+1 */ + if (c == PEOA) { /* 2^8+1 */ indx = 0; - else + } else #endif -#define U_C(c) ((unsigned char)(c)) - - if ((unsigned char)c >= (unsigned char)(CTLESC) - && (unsigned char)c <= (unsigned char)(CTLQUOTEMARK) - ) { - return CCTL; - } else { + { + if ((unsigned char)c >= CTLESC + && (unsigned char)c <= CTLQUOTEMARK + ) { + return CCTL; + } s = strchrnul(spec_symbls, c); - if (*s == '\0') + if (*s == '\0') { return CWORD; + } indx = syntax_index_table[s - spec_symbls]; } return S_I_T[indx][syntax]; @@ -2982,7 +3028,7 @@ static const char syntax_index_table[258] = { /* 257 127 */ CWORD_CWORD_CWORD_CWORD, }; -#define SIT(c, syntax) (S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax]) +#define SIT(c, syntax) (S_I_T[(int)syntax_index_table[(int)(c) + SYNBASE]][syntax]) #endif /* USE_SIT_FUNCTION */ @@ -3132,7 +3178,7 @@ printalias(const struct alias *ap) /* * TODO - sort output */ -static int +static int FAST_FUNC aliascmd(int argc UNUSED_PARAM, char **argv) { char *n, *v; @@ -3167,7 +3213,7 @@ aliascmd(int argc UNUSED_PARAM, char **argv) return ret; } -static int +static int FAST_FUNC unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { int i; @@ -3199,9 +3245,9 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #define FORK_NOJOB 2 /* mode flags for showjob(s) */ -#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ -#define SHOW_PID 0x04 /* include process pid */ -#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ +#define SHOW_ONLY_PGID 0x01 /* show only pgid (jobs -p) */ +#define SHOW_PIDS 0x02 /* show individual pids, not just one line per job */ +#define SHOW_CHANGED 0x04 /* only jobs whose state has changed */ /* * A job structure contains information about a job. A job is either a @@ -3209,7 +3255,6 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) * latter case, pidlist will be non-NULL, and will point to a -1 terminated * array of pids. */ - struct procstat { pid_t pid; /* process id */ int status; /* last process status from wait() */ @@ -3239,9 +3284,6 @@ struct job { }; static struct job *makejob(/*union node *,*/ int); -#if !JOBS -#define forkshell(job, node, mode) forkshell(job, mode) -#endif static int forkshell(struct job *, union node *, int); static int waitforjob(struct job *); @@ -3253,6 +3295,39 @@ static smallint doing_jobctl; //references:8 static void setjobctl(int); #endif +/* + * Ignore a signal. + */ +static void +ignoresig(int signo) +{ + /* Avoid unnecessary system calls. Is it already SIG_IGNed? */ + if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { + /* No, need to do it */ + signal(signo, SIG_IGN); + } + sigmode[signo - 1] = S_HARD_IGN; +} + +/* + * Signal handler. Only one usage site - in setsignal() + */ +static void +onsig(int signo) +{ + gotsig[signo - 1] = 1; + + if (signo == SIGINT && !trap[SIGINT]) { + if (!suppress_int) { + pending_sig = 0; + raise_interrupt(); /* does not return */ + } + pending_int = 1; + } else { + pending_sig = signo; + } +} + /* * Set the signal handler for the specified signal. The routine figures * out what it should be set to. @@ -3260,81 +3335,90 @@ static void setjobctl(int); static void setsignal(int signo) { - int action; - char *t, tsig; + char *t; + char cur_act, new_act; struct sigaction act; t = trap[signo]; - action = S_IGN; - if (t == NULL) - action = S_DFL; - else if (*t != '\0') - action = S_CATCH; - if (rootshell && action == S_DFL) { + new_act = S_DFL; + if (t != NULL) { /* trap for this sig is set */ + new_act = S_CATCH; + if (t[0] == '\0') /* trap is "": ignore this sig */ + new_act = S_IGN; + } + + if (rootshell && new_act == S_DFL) { switch (signo) { case SIGINT: if (iflag || minusc || sflag == 0) - action = S_CATCH; + new_act = S_CATCH; break; case SIGQUIT: #if DEBUG if (debug) break; #endif - /* FALLTHROUGH */ + /* man bash: + * "In all cases, bash ignores SIGQUIT. Non-builtin + * commands run by bash have signal handlers + * set to the values inherited by the shell + * from its parent". */ + new_act = S_IGN; + break; case SIGTERM: if (iflag) - action = S_IGN; + new_act = S_IGN; break; #if JOBS case SIGTSTP: case SIGTTOU: if (mflag) - action = S_IGN; + new_act = S_IGN; break; #endif } } +//TODO: if !rootshell, we reset SIGQUIT to DFL, +//whereas we have to restore it to what shell got on entry +//from the parent. See comment above t = &sigmode[signo - 1]; - tsig = *t; - if (tsig == 0) { - /* - * current setting unknown - */ - if (sigaction(signo, NULL, &act) == -1) { - /* - * Pretend it worked; maybe we should give a warning - * here, but other shells don't. We don't alter - * sigmode, so that we retry every time. - */ + cur_act = *t; + if (cur_act == 0) { + /* current setting is not yet known */ + if (sigaction(signo, NULL, &act)) { + /* pretend it worked; maybe we should give a warning, + * but other shells don't. We don't alter sigmode, + * so we retry every time. + * btw, in Linux it never fails. --vda */ return; } - tsig = S_RESET; /* force to be set */ if (act.sa_handler == SIG_IGN) { - tsig = S_HARD_IGN; + cur_act = S_HARD_IGN; if (mflag && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) ) { - tsig = S_IGN; /* don't hard ignore these */ + cur_act = S_IGN; /* don't hard ignore these */ } } } - if (tsig == S_HARD_IGN || tsig == action) + if (cur_act == S_HARD_IGN || cur_act == new_act) return; + act.sa_handler = SIG_DFL; - switch (action) { + switch (new_act) { case S_CATCH: act.sa_handler = onsig; + act.sa_flags = 0; /* matters only if !DFL and !IGN */ + sigfillset(&act.sa_mask); /* ditto */ break; case S_IGN: act.sa_handler = SIG_IGN; break; } - *t = action; - act.sa_flags = 0; - sigfillset(&act.sa_mask); sigaction_set(signo, &act); + + *t = new_act; } /* mode flags for set_curjob */ @@ -3427,7 +3511,7 @@ getjob(const char *name, int getctl) { struct job *jp; struct job *found; - const char *err_msg = "No such job: %s"; + const char *err_msg = "%s: no such job"; unsigned num; int c; const char *p; @@ -3463,7 +3547,6 @@ getjob(const char *name, int getctl) } if (is_number(p)) { -// TODO: number() instead? It does error checking... num = atoi(p); if (num < njobs) { jp = jobtab + num - 1; @@ -3479,10 +3562,8 @@ getjob(const char *name, int getctl) p++; } - found = 0; - while (1) { - if (!jp) - goto err; + found = NULL; + while (jp) { if (match(jp->ps[0].cmd, p)) { if (found) goto err; @@ -3491,6 +3572,9 @@ getjob(const char *name, int getctl) } jp = jp->prev_job; } + if (!found) + goto err; + jp = found; gotit: #if JOBS @@ -3609,7 +3693,7 @@ setjobctl(int on) doing_jobctl = on; } -static int +static int FAST_FUNC killcmd(int argc, char **argv) { int i = 1; @@ -3674,7 +3758,7 @@ restartjob(struct job *jp, int mode) return status; } -static int +static int FAST_FUNC fg_bgcmd(int argc UNUSED_PARAM, char **argv) { struct job *jp; @@ -3738,48 +3822,6 @@ sprint_status(char *s, int status, int sigonly) return col; } -/* - * Do a wait system call. If job control is compiled in, we accept - * stopped processes. If block is zero, we return a value of zero - * rather than blocking. - * - * System V doesn't have a non-blocking wait system call. It does - * have a SIGCLD signal that is sent to a process when one of it's - * children dies. The obvious way to use SIGCLD would be to install - * a handler for SIGCLD which simply bumped a counter when a SIGCLD - * was received, and have waitproc bump another counter when it got - * the status of a process. Waitproc would then know that a wait - * system call would not block if the two counters were different. - * This approach doesn't work because if a process has children that - * have not been waited for, System V will send it a SIGCLD when it - * installs a signal handler for SIGCLD. What this means is that when - * a child exits, the shell will be sent SIGCLD signals continuously - * until is runs out of stack space, unless it does a wait call before - * restoring the signal handler. The code below takes advantage of - * this (mis)feature by installing a signal handler for SIGCLD and - * then checking to see whether it was called. If there are any - * children to be waited for, it will be. - * - * If neither SYSV nor BSD is defined, we don't implement nonblocking - * waits at all. In this case, the user will not be informed when - * a background process until the next time she runs a real program - * (as opposed to running a builtin command or just typing return), - * and the jobs command may give out of date information. - */ -static int -waitproc(int wait_flags, int *status) -{ -#if JOBS - if (doing_jobctl) - wait_flags |= WUNTRACED; -#endif - /* NB: _not_ safe_waitpid, we need to detect EINTR */ - return waitpid(-1, status, wait_flags); -} - -/* - * Wait for a process to terminate. - */ static int dowait(int wait_flags, struct job *job) { @@ -3789,17 +3831,18 @@ dowait(int wait_flags, struct job *job) struct job *thisjob; int state; - TRACE(("dowait(%d) called\n", wait_flags)); - pid = waitproc(wait_flags, &status); - TRACE(("wait returns pid=%d, status=%d\n", pid, status)); - if (pid <= 0) { - /* If we were doing blocking wait and (probably) got EINTR, - * check for pending sigs received while waiting. - * (NB: can be moved into callers if needed) */ - if (wait_flags == DOWAIT_BLOCK && pendingsig) - raise_exception(EXSIG); + TRACE(("dowait(0x%x) called\n", wait_flags)); + + /* Do a wait system call. If job control is compiled in, we accept + * stopped processes. wait_flags may have WNOHANG, preventing blocking. + * NB: _not_ safe_waitpid, we need to detect EINTR */ + pid = waitpid(-1, &status, + (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags)); + TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", + pid, status, errno, strerror(errno))); + if (pid <= 0) return pid; - } + INT_OFF; thisjob = NULL; for (jp = curjob; jp; jp = jp->prev_job) { @@ -3871,9 +3914,18 @@ dowait(int wait_flags, struct job *job) return pid; } -#if JOBS -static void -showjob(FILE *out, struct job *jp, int mode) +static int +blocking_wait_with_raise_on_sig(struct job *job) +{ + pid_t pid = dowait(DOWAIT_BLOCK, job); + if (pid <= 0 && pending_sig) + raise_exception(EXSIG); + return pid; +} + +#if JOBS +static void +showjob(FILE *out, struct job *jp, int mode) { struct procstat *ps; struct procstat *psend; @@ -3883,7 +3935,7 @@ showjob(FILE *out, struct job *jp, int mode) ps = jp->ps; - if (mode & SHOW_PGID) { + if (mode & SHOW_ONLY_PGID) { /* jobs -p */ /* just output process (group) id of pipeline */ fprintf(out, "%d\n", ps->pid); return; @@ -3893,11 +3945,11 @@ showjob(FILE *out, struct job *jp, int mode) indent_col = col; if (jp == curjob) - s[col - 2] = '+'; + s[col - 3] = '+'; else if (curjob && jp == curjob->prev_job) - s[col - 2] = '-'; + s[col - 3] = '-'; - if (mode & SHOW_PID) + if (mode & SHOW_PIDS) col += fmtstr(s + col, 16, "%d ", ps->pid); psend = ps + jp->nprocs; @@ -3911,25 +3963,32 @@ showjob(FILE *out, struct job *jp, int mode) status = jp->stopstatus; col += sprint_status(s + col, status, 0); } + /* By now, "[JOBID]* [maybe PID] STATUS" is printed */ + /* This loop either prints " | | " line + * or prints several "PID | " lines, + * depending on SHOW_PIDS bit. + * We do not print status of individual processes + * between PID and . bash does it, but not very well: + * first line shows overall job status, not process status, + * making it impossible to know 1st process status. + */ goto start; - - do { + while (1) { /* for each process */ - col = fmtstr(s, 48, " |\n%*c%d ", indent_col, ' ', ps->pid) - 3; + s[0] = '\0'; + col = 33; + if (mode & SHOW_PIDS) + col = fmtstr(s, 48, "\n%*c%d ", indent_col, ' ', ps->pid) - 1; start: - fprintf(out, "%s%*c%s", - s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd - ); - if (!(mode & SHOW_PID)) { - showpipe(jp, out); - break; - } - if (++ps == psend) { - outcslow('\n', out); + fprintf(out, "%s%*c", s, 33 - col >= 0 ? 33 - col : 0, ' '); + if (ps != jp->ps) + fprintf(out, "| "); + fprintf(out, "%s", ps->cmd); + if (++ps == psend) break; - } - } while (1); + } + outcslow('\n', out); jp->changed = 0; @@ -3948,9 +4007,9 @@ showjobs(FILE *out, int mode) { struct job *jp; - TRACE(("showjobs(%x) called\n", mode)); + TRACE(("showjobs(0x%x) called\n", mode)); - /* If not even one job changed, there is nothing to do */ + /* Handle all finished jobs */ while (dowait(DOWAIT_NONBLOCK, NULL) > 0) continue; @@ -3961,23 +4020,23 @@ showjobs(FILE *out, int mode) } } -static int +static int FAST_FUNC jobscmd(int argc UNUSED_PARAM, char **argv) { int mode, m; mode = 0; - while ((m = nextopt("lp"))) { + while ((m = nextopt("lp")) != '\0') { if (m == 'l') - mode = SHOW_PID; + mode |= SHOW_PIDS; else - mode = SHOW_PGID; + mode |= SHOW_ONLY_PGID; } argv = argptr; if (*argv) { do - showjob(stdout, getjob(*argv,0), mode); + showjob(stdout, getjob(*argv, 0), mode); while (*++argv); } else showjobs(stdout, mode); @@ -4009,21 +4068,19 @@ getstatus(struct job *job) } retval += 128; } - TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", + TRACE(("getstatus: job %d, nproc %d, status 0x%x, retval 0x%x\n", jobno(job), job->nprocs, status, retval)); return retval; } -static int +static int FAST_FUNC waitcmd(int argc UNUSED_PARAM, char **argv) { struct job *job; int retval; struct job *jp; -// exsig++; -// xbarrier(); - if (pendingsig) + if (pending_sig) raise_exception(EXSIG); nextopt(nullstr); @@ -4042,7 +4099,14 @@ waitcmd(int argc UNUSED_PARAM, char **argv) jp->waited = 1; jp = jp->prev_job; } - dowait(DOWAIT_BLOCK, NULL); + /* man bash: + * "When bash is waiting for an asynchronous command via + * the wait builtin, the reception of a signal for which a trap + * has been set will cause the wait builtin to return immediately + * with an exit status greater than 128, immediately after which + * the trap is executed." + * Do we do it that way? */ + blocking_wait_with_raise_on_sig(NULL); } } @@ -4062,11 +4126,10 @@ waitcmd(int argc UNUSED_PARAM, char **argv) job = getjob(*argv, 0); /* loop until process terminated or stopped */ while (job->state == JOBRUNNING) - dowait(DOWAIT_BLOCK, NULL); + blocking_wait_with_raise_on_sig(NULL); job->waited = 1; retval = getstatus(job); - repeat: - ; + repeat: ; } while (*++argv); ret: @@ -4174,7 +4237,7 @@ cmdputs(const char *s) static const char vstype[VSTYPE + 1][3] = { "", "}", "-", "+", "?", "=", "%", "%%", "#", "##" - USE_ASH_BASH_COMPAT(, ":", "/", "//") + IF_ASH_BASH_COMPAT(, ":", "/", "//") }; const char *p, *str; @@ -4213,7 +4276,7 @@ cmdputs(const char *s) case CTLBACKQ+CTLQUOTE: str = "\"$(...)\""; goto dostr; -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT case CTLARI: str = "$(("; goto dostr; @@ -4253,7 +4316,7 @@ cmdputs(const char *s) if (!str) continue; dostr: - while ((c = *str++)) { + while ((c = *str++) != '\0') { USTPUTC(c, nextc); } } @@ -4285,7 +4348,6 @@ cmdtxt(union node *n) union node *np; struct nodelist *lp; const char *p; - char s[2]; if (!n) return; @@ -4331,11 +4393,12 @@ cmdtxt(union node *n) cmdputs("if "); cmdtxt(n->nif.test); cmdputs("; then "); - n = n->nif.ifpart; if (n->nif.elsepart) { - cmdtxt(n); + cmdtxt(n->nif.ifpart); cmdputs("; else "); n = n->nif.elsepart; + } else { + n = n->nif.ifpart; } p = "; fi"; goto dotail; @@ -4405,6 +4468,9 @@ cmdtxt(union node *n) case NAPPEND: p = ">>"; goto redir; +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif case NTOFD: p = ">&"; goto redir; @@ -4417,14 +4483,11 @@ cmdtxt(union node *n) case NFROMTO: p = "<>"; redir: - s[0] = n->nfile.fd + '0'; - s[1] = '\0'; - cmdputs(s); + cmdputs(utoa(n->nfile.fd)); cmdputs(p); if (n->type == NTOFD || n->type == NFROMFD) { - s[0] = n->ndup.dupfd + '0'; - p = s; - goto dotail2; + cmdputs(utoa(n->ndup.dupfd)); + break; } n = n->nfile.fname; goto donode; @@ -4472,9 +4535,11 @@ clear_traps(void) for (tp = trap; tp < &trap[NSIG]; tp++) { if (*tp && **tp) { /* trap not NULL or "" (SIG_IGN) */ INT_OFF; - free(*tp); + if (trap_ptr == trap) + free(*tp); + /* else: it "belongs" to trap_ptr vector, don't free */ *tp = NULL; - if (tp != &trap[0]) + if ((tp - trap) != 0) setsignal(tp - trap); INT_ON; } @@ -4485,8 +4550,8 @@ clear_traps(void) static void closescript(void); /* Called after fork(), in child */ -static void -forkchild(struct job *jp, /*union node *n,*/ int mode) +static NOINLINE void +forkchild(struct job *jp, union node *n, int mode) { int oldlvl; @@ -4494,7 +4559,58 @@ forkchild(struct job *jp, /*union node *n,*/ int mode) oldlvl = shlvl; shlvl++; + /* man bash: "Non-builtin commands run by bash have signal handlers + * set to the values inherited by the shell from its parent". + * Do we do it correctly? */ + closescript(); + + if (mode == FORK_NOJOB /* is it `xxx` ? */ + && n && n->type == NCMD /* is it single cmd? */ + /* && n->ncmd.args->type == NARG - always true? */ + && strcmp(n->ncmd.args->narg.text, "trap") == 0 + && n->ncmd.args->narg.next == NULL /* "trap" with no arguments */ + /* && n->ncmd.args->narg.backquote == NULL - do we need to check this? */ + ) { + TRACE(("Trap hack\n")); + /* Awful hack for `trap` or $(trap). + * + * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html + * contains an example where "trap" is executed in a subshell: + * + * save_traps=$(trap) + * ... + * eval "$save_traps" + * + * Standard does not say that "trap" in subshell shall print + * parent shell's traps. It only says that its output + * must have suitable form, but then, in the above example + * (which is not supposed to be normative), it implies that. + * + * bash (and probably other shell) does implement it + * (traps are reset to defaults, but "trap" still shows them), + * but as a result, "trap" logic is hopelessly messed up: + * + * # trap + * trap -- 'echo Ho' SIGWINCH <--- we have a handler + * # (trap) <--- trap is in subshell - no output (correct, traps are reset) + * # true | trap <--- trap is in subshell - no output (ditto) + * # echo `true | trap` <--- in subshell - output (but traps are reset!) + * trap -- 'echo Ho' SIGWINCH + * # echo `(trap)` <--- in subshell in subshell - output + * trap -- 'echo Ho' SIGWINCH + * # echo `true | (trap)` <--- in subshell in subshell in subshell - output! + * trap -- 'echo Ho' SIGWINCH + * + * The rules when to forget and when to not forget traps + * get really complex and nonsensical. + * + * Our solution: ONLY bare $(trap) or `trap` is special. + */ + /* Save trap handler strings for trap builtin to print */ + trap_ptr = memcpy(xmalloc(sizeof(trap)), trap, sizeof(trap)); + /* Fall through into clearing traps */ + } clear_traps(); #if JOBS /* do job control only in root shell */ @@ -4506,8 +4622,8 @@ forkchild(struct job *jp, /*union node *n,*/ int mode) pgrp = getpid(); else pgrp = jp->ps[0].pid; - /* This can fail because we are doing it in the parent also */ - (void)setpgid(0, pgrp); + /* this can fail because we are doing it in the parent also */ + setpgid(0, pgrp); if (mode == FORK_FG) xtcsetpgrp(ttyfd, pgrp); setsignal(SIGTSTP); @@ -4515,19 +4631,42 @@ forkchild(struct job *jp, /*union node *n,*/ int mode) } else #endif if (mode == FORK_BG) { + /* man bash: "When job control is not in effect, + * asynchronous commands ignore SIGINT and SIGQUIT" */ ignoresig(SIGINT); ignoresig(SIGQUIT); 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_error("can't open '%s'", bb_dev_null); } } - if (!oldlvl && iflag) { - setsignal(SIGINT); + if (!oldlvl) { + if (iflag) { /* why if iflag only? */ + setsignal(SIGINT); + setsignal(SIGTERM); + } + /* man bash: + * "In all cases, bash ignores SIGQUIT. Non-builtin + * commands run by bash have signal handlers + * set to the values inherited by the shell + * from its parent". + * Take care of the second rule: */ setsignal(SIGQUIT); - setsignal(SIGTERM); } +#if JOBS + if (n && n->type == NCMD + && strcmp(n->ncmd.args->narg.text, "jobs") == 0 + ) { + TRACE(("Job hack\n")); + /* "jobs": we do not want to clear job list for it, + * instead we remove only _its_ own_ job from job list. + * This makes "jobs .... | cat" more useful. + */ + freejob(curjob); + return; + } +#endif for (jp = curjob; jp; jp = jp->prev_job) freejob(jp); jobless = 0; @@ -4588,22 +4727,24 @@ forkshell(struct job *jp, union node *n, int mode) freejob(jp); ash_msg_and_raise_error("can't fork"); } - if (pid == 0) - forkchild(jp, /*n,*/ mode); - else + if (pid == 0) { + CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */ + forkchild(jp, n, mode); + } else { forkparent(jp, n, mode, pid); + } return pid; } /* * Wait for job to finish. * - * Under job control we have the problem that while a child process is - * running interrupts generated by the user are sent to the child but not - * to the shell. This means that an infinite loop started by an inter- - * active user may be hard to kill. With job control turned off, an - * interactive user may place an interactive program inside a loop. If - * the interactive program catches interrupts, the user doesn't want + * Under job control we have the problem that while a child process + * is running interrupts generated by the user are sent to the child + * but not to the shell. This means that an infinite loop started by + * an interactive user may be hard to kill. With job control turned off, + * an interactive user may place an interactive program inside a loop. + * If the interactive program catches interrupts, the user doesn't want * these interrupts to also abort the loop. The approach we take here * is to have the shell ignore interrupt signals while waiting for a * foreground process to terminate, and then send itself an interrupt @@ -4621,9 +4762,43 @@ waitforjob(struct job *jp) int st; TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + + 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; + st = getstatus(jp); #if JOBS if (jp->jobctl) { @@ -4675,11 +4850,6 @@ stoppedjobs(void) #define EMPTY -2 /* marks an unused slot in redirtab */ #define CLOSED -3 /* marks a slot of previously-closed fd */ -#ifndef PIPE_BUF -# define PIPESIZE 4096 /* amount of buffering in a pipe */ -#else -# define PIPESIZE PIPE_BUF -#endif /* * Open a file in noclobber mode. @@ -4756,7 +4926,7 @@ openhere(union node *redir) ash_msg_and_raise_error("pipe call failed"); if (redir->type == NHERE) { len = strlen(redir->nhere.doc->narg.text); - if (len <= PIPESIZE) { + if (len <= PIPE_BUF) { full_write(pip[1], redir->nhere.doc->narg.text, len); goto out; } @@ -4764,12 +4934,10 @@ openhere(union node *redir) if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { /* child */ close(pip[0]); - signal(SIGINT, SIG_IGN); - signal(SIGQUIT, SIG_IGN); - signal(SIGHUP, SIG_IGN); -#ifdef SIGTSTP - signal(SIGTSTP, SIG_IGN); -#endif + ignoresig(SIGINT); //signal(SIGINT, SIG_IGN); + ignoresig(SIGQUIT); //signal(SIGQUIT, SIG_IGN); + ignoresig(SIGHUP); //signal(SIGHUP, SIG_IGN); + ignoresig(SIGTSTP); //signal(SIGTSTP, SIG_IGN); signal(SIGPIPE, SIG_DFL); if (redir->type == NHERE) full_write(pip[1], redir->nhere.doc->narg.text, len); @@ -4802,6 +4970,9 @@ openredirect(union node *redir) goto ecreate; break; case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif /* Take care of noclobber mode. */ if (Cflag) { fname = redir->nfile.expfname; @@ -4851,28 +5022,87 @@ openredirect(union node *redir) * if the source file descriptor is closed, EMPTY if there are no unused * file descriptors left. */ +/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD). + * old code was doing close(to) prior to copyfd() to achieve the same */ +enum { + COPYFD_EXACT = (int)~(INT_MAX), + COPYFD_RESTORE = (int)((unsigned)COPYFD_EXACT >> 1), +}; static int copyfd(int from, int to) { int newfd; - newfd = fcntl(from, F_DUPFD, to); + if (to & COPYFD_EXACT) { + to &= ~COPYFD_EXACT; + /*if (from != to)*/ + newfd = dup2(from, to); + } else { + newfd = fcntl(from, F_DUPFD, to); + } if (newfd < 0) { if (errno == EMFILE) return EMPTY; + /* Happens when source fd is not open: try "echo >&99" */ ash_msg_and_raise_error("%d: %m", from); } return newfd; } /* Struct def and variable are moved down to the first usage site */ +struct two_fd_t { + int orig, copy; +}; struct redirtab { struct redirtab *next; - int renamed[10]; int nullredirs; + int pair_count; + struct two_fd_t two_fd[]; }; #define redirlist (G_var.redirlist) +static int need_to_remember(struct redirtab *rp, int fd) +{ + int i; + + if (!rp) /* remembering was not requested */ + return 0; + + for (i = 0; i < rp->pair_count; i++) { + if (rp->two_fd[i].orig == fd) { + /* already remembered */ + return 0; + } + } + return 1; +} + +/* "hidden" fd is a fd used to read scripts, or a copy of such */ +static int is_hidden_fd(struct redirtab *rp, int fd) +{ + int i; + struct parsefile *pf; + + if (fd == -1) + return 0; + pf = g_parsefile; + while (pf) { + if (fd == pf->fd) { + return 1; + } + pf = pf->prev; + } + if (!rp) + return 0; + fd |= COPYFD_RESTORE; + for (i = 0; i < rp->pair_count; i++) { + if (rp->two_fd[i].copy == fd) { + return 1; + } + } + return 0; +} + /* * Process a list of redirection commands. If the REDIR_PUSH flag is set, * old file descriptors are stashed away so that the redirection can be @@ -4887,88 +5117,138 @@ static void redirect(union node *redir, int flags) { struct redirtab *sv; + int sv_pos; int i; int fd; int newfd; + int copied_fd2 = -1; g_nullredirs++; if (!redir) { return; } + sv = NULL; + sv_pos = 0; INT_OFF; if (flags & REDIR_PUSH) { - sv = ckmalloc(sizeof(*sv)); + union node *tmp = redir; + do { + sv_pos++; +#if ENABLE_ASH_BASH_COMPAT + if (redir->nfile.type == NTO2) + sv_pos++; +#endif + tmp = tmp->nfile.next; + } while (tmp); + sv = ckmalloc(sizeof(*sv) + sv_pos * sizeof(sv->two_fd[0])); sv->next = redirlist; + sv->pair_count = sv_pos; redirlist = sv; sv->nullredirs = g_nullredirs - 1; g_nullredirs = 0; - for (i = 0; i < 10; i++) - sv->renamed[i] = EMPTY; + while (sv_pos > 0) { + sv_pos--; + sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY; + } } do { fd = redir->nfile.fd; if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { - if (redir->ndup.dupfd == fd) - continue; /* redirect from/to same file descriptor */ + int right_fd = redir->ndup.dupfd; + /* redirect from/to same file descriptor? */ + if (right_fd == fd) + continue; + /* echo >&10 and 10 is a fd opened to the sh script? */ + if (is_hidden_fd(sv, right_fd)) { + errno = EBADF; /* as if it is closed */ + ash_msg_and_raise_error("%d: %m", right_fd); + } newfd = -1; } else { newfd = openredirect(redir); /* always >= 0 */ if (fd == newfd) { /* Descriptor wasn't open before redirect. * Mark it for close in the future */ - if (sv && sv->renamed[fd] == EMPTY) - sv->renamed[fd] = CLOSED; + if (need_to_remember(sv, fd)) { + goto remember_to_close; + } continue; } } - if (sv && sv->renamed[fd] == EMPTY) { +#if ENABLE_ASH_BASH_COMPAT + redirect_more: +#endif + if (need_to_remember(sv, fd)) { /* Copy old descriptor */ i = fcntl(fd, F_DUPFD, 10); +/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds + * are closed in popredir() in the child, preventing them from leaking + * into child. (popredir() also cleans up the mess in case of failures) + */ if (i == -1) { i = errno; if (i != EBADF) { /* Strange error (e.g. "too many files" EMFILE?) */ - /*if (newfd >= 0)*/ close(newfd); + if (newfd >= 0) + close(newfd); errno = i; ash_msg_and_raise_error("%d: %m", fd); /* NOTREACHED */ } - /* EBADF: it is not open - ok */ - } else { - /* fd is open, save its copy */ -//TODO: CLOEXEC the copy? currently these extra "saved" fds are closed -// in popredir() in the child, preventing them from leaking into child. -// (popredir() also cleans up the mess in case of failures) - sv->renamed[fd] = i; - close(fd); + /* EBADF: it is not open - good, remember to close it */ + remember_to_close: + i = CLOSED; + } else { /* fd is open, save its copy */ + /* "exec fd>&-" should not close fds + * which point to script file(s). + * Force them to be restored afterwards */ + if (is_hidden_fd(sv, fd)) + i |= COPYFD_RESTORE; } - } else { - close(fd); + if (fd == 2) + copied_fd2 = i; + sv->two_fd[sv_pos].orig = fd; + sv->two_fd[sv_pos].copy = i; + sv_pos++; } - /* At this point fd is closed */ if (newfd < 0) { /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */ - if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ - copyfd(redir->ndup.dupfd, fd); + if (redir->ndup.dupfd < 0) { /* "fd>&-" */ + /* Don't want to trigger debugging */ + if (fd != -1) + close(fd); + } else { + copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT); } - } else { /* move newfd to fd */ - copyfd(newfd, fd); - close(newfd); + } else if (fd != newfd) { /* move newfd to fd */ + copyfd(newfd, fd | COPYFD_EXACT); +#if ENABLE_ASH_BASH_COMPAT + if (!(redir->nfile.type == NTO2 && fd == 2)) +#endif + close(newfd); + } +#if ENABLE_ASH_BASH_COMPAT + if (redir->nfile.type == NTO2 && fd == 1) { + /* We already redirected it to fd 1, now copy it to 2 */ + newfd = 1; + fd = 2; + goto redirect_more; } +#endif } while ((redir = redir->nfile.next) != NULL); INT_ON; - if ((flags & REDIR_SAVEFD2) && sv && sv->renamed[2] >= 0) - preverrout_fd = sv->renamed[2]; + if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0) + preverrout_fd = copied_fd2; } /* * Undo the effects of the last redirection. */ static void -popredir(int drop) +popredir(int drop, int restore) { struct redirtab *rp; int i; @@ -4977,18 +5257,21 @@ popredir(int drop) return; INT_OFF; rp = redirlist; - for (i = 0; i < 10; i++) { - if (rp->renamed[i] == CLOSED) { + for (i = 0; i < rp->pair_count; i++) { + int fd = rp->two_fd[i].orig; + int copy = rp->two_fd[i].copy; + if (copy == CLOSED) { if (!drop) - close(i); + close(fd); continue; } - if (rp->renamed[i] != EMPTY) { - if (!drop) { - close(i); - copyfd(rp->renamed[i], i); + if (copy != EMPTY) { + if (!drop || (restore && (copy & COPYFD_RESTORE))) { + copy &= ~COPYFD_RESTORE; + /*close(fd);*/ + copyfd(copy, fd | COPYFD_EXACT); } - close(rp->renamed[i]); + close(copy & ~COPYFD_RESTORE); } } redirlist = rp->next; @@ -5011,7 +5294,7 @@ clearredir(int drop) g_nullredirs = 0; if (!redirlist) break; - popredir(drop); + popredir(drop, /*restore:*/ 0); } } @@ -5024,13 +5307,14 @@ redirectsafe(union node *redir, int flags) struct jmploc jmploc; SAVE_INT(saveint); - err = setjmp(jmploc.loc) * 2; + /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */ + err = setjmp(jmploc.loc); // huh?? was = setjmp(jmploc.loc) * 2; if (!err) { exception_handler = &jmploc; redirect(redir, flags); } exception_handler = savehandler; - if (err && exception != EXERROR) + if (err && exception_type != EXERROR) longjmp(exception_handler->loc, 1); RESTORE_INT(saveint); return err; @@ -5042,17 +5326,33 @@ redirectsafe(union node *redir, int flags) * We have to deal with backquotes, shell variables, and file metacharacters. */ -#if ENABLE_ASH_MATH_SUPPORT_64 -typedef int64_t arith_t; -#define arith_t_type long long -#else -typedef long arith_t; -#define arith_t_type long -#endif +#if ENABLE_SH_MATH_SUPPORT +static arith_t +ash_arith(const char *s) +{ + arith_eval_hooks_t math_hooks; + arith_t result; + int errcode = 0; + + math_hooks.lookupvar = lookupvar; + math_hooks.setvar = setvar; + math_hooks.endofname = endofname; + + INT_OFF; + result = arith(s, &errcode, &math_hooks); + if (errcode < 0) { + if (errcode == -3) + ash_msg_and_raise_error("exponent less than 0"); + if (errcode == -2) + ash_msg_and_raise_error("divide by zero"); + if (errcode == -5) + ash_msg_and_raise_error("expression recursion loop detected"); + raise_error_syntax(s); + } + INT_ON; -#if ENABLE_ASH_MATH_SUPPORT -static arith_t dash_arith(const char *); -static arith_t arith(const char *expr, int *perrcode); + return result; +} #endif /* @@ -5068,7 +5368,7 @@ static arith_t arith(const char *expr, int *perrcode); #define EXP_WORD 0x80 /* expand word in parameter expansion */ #define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ /* - * _rmescape() flags + * rmescape() flags */ #define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ #define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ @@ -5112,11 +5412,7 @@ cvtnum(arith_t num) int len; expdest = makestrspace(32, expdest); -#if ENABLE_ASH_MATH_SUPPORT_64 - len = fmtstr(expdest, 32, "%lld", (long long) num); -#else - len = fmtstr(expdest, 32, "%ld", num); -#endif + len = fmtstr(expdest, 32, arith_t_fmt, num); STADJUST(len, expdest); return len; } @@ -5126,7 +5422,7 @@ esclen(const char *start, const char *p) { size_t esc = 0; - while (p > start && *--p == CTLESC) { + while (p > start && (unsigned char)*--p == CTLESC) { esc++; } return esc; @@ -5136,19 +5432,19 @@ esclen(const char *start, const char *p) * Remove any CTLESC characters from a string. */ static char * -_rmescapes(char *str, int flag) +rmescapes(char *str, int flag) { static const char qchars[] ALIGN1 = { CTLESC, CTLQUOTEMARK, '\0' }; char *p, *q, *r; unsigned inquotes; - int notescaped; - int globbing; + unsigned protect_against_glob; + unsigned globbing; p = strpbrk(str, qchars); - if (!p) { + if (!p) return str; - } + q = p; r = str; if (flag & RMESCAPE_ALLOC) { @@ -5167,28 +5463,33 @@ _rmescapes(char *str, int flag) q = (char *)memcpy(q, str, len) + len; } } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; globbing = flag & RMESCAPE_GLOB; - notescaped = globbing; + protect_against_glob = globbing; while (*p) { if (*p == CTLQUOTEMARK) { +// TODO: if no RMESCAPE_QUOTED in flags, inquotes never becomes 0 +// (alternates between RMESCAPE_QUOTED and ~RMESCAPE_QUOTED). Is it ok? +// Note: both inquotes and protect_against_glob only affect whether +// CTLESC, gets converted to or to \ inquotes = ~inquotes; p++; - notescaped = globbing; + protect_against_glob = globbing; continue; } if (*p == '\\') { /* naked back slash */ - notescaped = 0; + protect_against_glob = 0; goto copy; } if (*p == CTLESC) { p++; - if (notescaped && inquotes && *p != '/') { + if (protect_against_glob && inquotes && *p != '/') { *q++ = '\\'; } } - notescaped = globbing; + protect_against_glob = globbing; copy: *q++ = *p++; } @@ -5199,8 +5500,6 @@ _rmescapes(char *str, int flag) } return r; } -#define rmescapes(p) _rmescapes((p), 0) - #define pmatch(a, b) !fnmatch((a), (b), 0) /* @@ -5215,7 +5514,7 @@ preglob(const char *pattern, int quoted, int flag) if (quoted) { flag |= RMESCAPE_QUOTED; } - return _rmescapes((char *)pattern, flag); + return rmescapes((char *)pattern, flag); } /* @@ -5226,14 +5525,17 @@ memtodest(const char *p, size_t len, int syntax, int quotes) { char *q = expdest; - q = makestrspace(len * 2, q); + q = makestrspace(quotes ? len * 2 : len, q); while (len--) { int c = signed_char2int(*p++); if (!c) continue; - if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) - USTPUTC(CTLESC, q); + if (quotes) { + int n = SIT(c, syntax); + if (n == CCTL || n == CBACK) + USTPUTC(CTLESC, q); + } USTPUTC(c, q); } @@ -5310,13 +5612,13 @@ removerecordregions(int endoff) } static char * -exptilde(char *startp, char *p, int flag) +exptilde(char *startp, char *p, int flags) { char c; char *name; struct passwd *pw; const char *home; - int quotes = flag & (EXP_FULL | EXP_CASE); + int quotes = flags & (EXP_FULL | EXP_CASE | EXP_REDIR); int startloc; name = p + 1; @@ -5328,7 +5630,7 @@ exptilde(char *startp, char *p, int flag) case CTLQUOTEMARK: return startp; case ':': - if (flag & EXP_VARTILDE) + if (flags & EXP_VARTILDE) goto done; break; case '/': @@ -5376,7 +5678,7 @@ static uint8_t back_exitstatus; /* exit status of backquoted command */ #define EV_EXIT 01 /* exit after evaluating tree */ static void evaltree(union node *, int); -static void +static void FAST_FUNC evalbackcmd(union node *n, struct backcmd *result) { int saveherefd; @@ -5385,9 +5687,8 @@ evalbackcmd(union node *n, struct backcmd *result) result->buf = NULL; result->nleft = 0; result->jp = NULL; - if (n == NULL) { + if (n == NULL) goto out; - } saveherefd = herefd; herefd = -1; @@ -5403,8 +5704,8 @@ evalbackcmd(union node *n, struct backcmd *result) FORCE_INT_ON; close(pip[0]); if (pip[1] != 1) { - close(1); - copyfd(pip[1], 1); + /*close(1);*/ + copyfd(pip[1], 1 | COPYFD_EXACT); close(pip[1]); } eflag = 0; @@ -5481,7 +5782,7 @@ expbackq(union node *cmd, int quoted, int quotes) stackblock() + startloc)); } -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT /* * Expand arithmetic expression. Backup to start of expression, * evaluate, place result in (backed up) result, adjust string position. @@ -5494,7 +5795,7 @@ expari(int quotes) int flag; int len; - /* ifsfree(); */ + /* ifsfree(); */ /* * This routine is slightly over-complicated for @@ -5534,9 +5835,9 @@ expari(int quotes) expdest = p; if (quotes) - rmescapes(p + 2); + rmescapes(p + 2, 0); - len = cvtnum(dash_arith(p + 2)); + len = cvtnum(ash_arith(p + 2)); if (flag != '"') recordregion(begoff, begoff + len, 0); @@ -5544,7 +5845,7 @@ expari(int quotes) #endif /* argstr needs it */ -static char *evalvar(char *p, int flag, struct strlist *var_str_list); +static char *evalvar(char *p, int flags, struct strlist *var_str_list); /* * Perform variable and command substitution. If EXP_FULL is set, output CTLESC @@ -5556,7 +5857,7 @@ static char *evalvar(char *p, int flag, struct strlist *var_str_list); * for correct expansion of "B=$A" word. */ static void -argstr(char *p, int flag, struct strlist *var_str_list) +argstr(char *p, int flags, struct strlist *var_str_list) { static const char spclchars[] ALIGN1 = { '=', @@ -5567,49 +5868,51 @@ argstr(char *p, int flag, struct strlist *var_str_list) CTLVAR, CTLBACKQ, CTLBACKQ | CTLQUOTE, -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT CTLENDARI, #endif 0 }; const char *reject = spclchars; int c; - int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ - int breakall = flag & EXP_WORD; + int quotes = flags & (EXP_FULL | EXP_CASE | EXP_REDIR); /* do CTLESC */ + int breakall = flags & EXP_WORD; int inquotes; size_t length; int startloc; - if (!(flag & EXP_VARTILDE)) { + if (!(flags & EXP_VARTILDE)) { reject += 2; - } else if (flag & EXP_VARTILDE2) { + } else if (flags & EXP_VARTILDE2) { reject++; } inquotes = 0; length = 0; - if (flag & EXP_TILDE) { + if (flags & EXP_TILDE) { char *q; - flag &= ~EXP_TILDE; + flags &= ~EXP_TILDE; tilde: q = p; - if (*q == CTLESC && (flag & EXP_QWORD)) + if (*q == CTLESC && (flags & EXP_QWORD)) q++; if (*q == '~') - p = exptilde(p, q, flag); + p = exptilde(p, q, flags); } start: startloc = expdest - (char *)stackblock(); for (;;) { length += strcspn(p + length, reject); - c = p[length]; - if (c && (!(c & 0x80) -#if ENABLE_ASH_MATH_SUPPORT - || c == CTLENDARI + c = (unsigned char) p[length]; + if (c) { + if (!(c & 0x80) +#if ENABLE_SH_MATH_SUPPORT + || c == CTLENDARI #endif - )) { - /* c == '=' || c == ':' || c == CTLENDARI */ - length++; + ) { + /* c == '=' || c == ':' || c == CTLENDARI */ + length++; + } } if (length > 0) { int newloc; @@ -5627,11 +5930,11 @@ argstr(char *p, int flag, struct strlist *var_str_list) case '\0': goto breakloop; case '=': - if (flag & EXP_VARTILDE2) { + if (flags & EXP_VARTILDE2) { p--; continue; } - flag |= EXP_VARTILDE2; + flags |= EXP_VARTILDE2; reject++; /* fall through */ case ':': @@ -5650,15 +5953,13 @@ argstr(char *p, int flag, struct strlist *var_str_list) goto breakloop; case CTLQUOTEMARK: /* "$@" syntax adherence hack */ - if ( - !inquotes && - !memcmp(p, dolatstr, 4) && - (p[4] == CTLQUOTEMARK || ( - p[4] == CTLENDVAR && - p[5] == CTLQUOTEMARK - )) + if (!inquotes + && memcmp(p, dolatstr, 4) == 0 + && ( p[4] == CTLQUOTEMARK + || (p[4] == CTLENDVAR && p[5] == CTLQUOTEMARK) + ) ) { - p = evalvar(p + 1, flag, /* var_str_list: */ NULL) + 1; + p = evalvar(p + 1, flags, /* var_str_list: */ NULL) + 1; goto start; } inquotes = !inquotes; @@ -5674,15 +5975,15 @@ argstr(char *p, int flag, struct strlist *var_str_list) length++; goto addquote; case CTLVAR: - p = evalvar(p, flag, var_str_list); + p = evalvar(p, flags, var_str_list); goto start; case CTLBACKQ: - c = 0; + c = '\0'; case CTLBACKQ|CTLQUOTE: expbackq(argbackq->n, c, quotes); argbackq = argbackq->next; goto start; -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT case CTLENDARI: p--; expari(quotes); @@ -5812,8 +6113,9 @@ varunset(const char *end, const char *var, const char *umsg, int varflags) if (*end == CTLENDVAR) { if (varflags & VSNUL) tail = " or null"; - } else + } else { msg = umsg; + } } ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail); } @@ -5856,9 +6158,9 @@ subevalvar(char *p, char *str, int strloc, int subtype, char *startp; char *loc; char *rmesc, *rmescend; - USE_ASH_BASH_COMPAT(char *repl = NULL;) - USE_ASH_BASH_COMPAT(char null = '\0';) - USE_ASH_BASH_COMPAT(int pos, len, orig_len;) + IF_ASH_BASH_COMPAT(char *repl = NULL;) + IF_ASH_BASH_COMPAT(char null = '\0';) + IF_ASH_BASH_COMPAT(int pos, len, orig_len;) int saveherefd = herefd; int amount, workloc, resetloc; int zero; @@ -5882,17 +6184,17 @@ subevalvar(char *p, char *str, int strloc, int subtype, #if ENABLE_ASH_BASH_COMPAT case VSSUBSTR: loc = str = stackblock() + strloc; -// TODO: number() instead? It does error checking... - pos = atoi(loc); + /* Read POS in ${var:POS:LEN} */ + pos = atoi(loc); /* number(loc) errors out on "1:4" */ len = str - startp - 1; /* *loc != '\0', guaranteed by parser */ if (quotes) { char *ptr; - /* We must adjust the length by the number of escapes we find. */ + /* Adjust the length by the number of escapes */ for (ptr = startp; ptr < (str - 1); ptr++) { - if(*ptr == CTLESC) { + if (*ptr == CTLESC) { len--; ptr++; } @@ -5901,15 +6203,22 @@ subevalvar(char *p, char *str, int strloc, int subtype, orig_len = len; if (*loc++ == ':') { -// TODO: number() instead? It does error checking... - len = atoi(loc); + /* ${var::LEN} */ + len = number(loc); } else { + /* Skip POS in ${var:POS:LEN} */ len = orig_len; - while (*loc && *loc != ':') + while (*loc && *loc != ':') { + /* TODO? + * bash complains on: var=qwe; echo ${var:1a:123} + if (!isdigit(*loc)) + ash_msg_and_raise_error(msg_illnum, str); + */ loc++; - if (*loc++ == ':') -// TODO: number() instead? It does error checking... - len = atoi(loc); + } + if (*loc++ == ':') { + len = number(loc); + } } if (pos >= orig_len) { pos = 0; @@ -5944,7 +6253,7 @@ subevalvar(char *p, char *str, int strloc, int subtype, * stack will need rebasing, and we'll need to remove our work * areas each time */ - USE_ASH_BASH_COMPAT(restart:) + IF_ASH_BASH_COMPAT(restart:) amount = expdest - ((char *)stackblock() + resetloc); STADJUST(-amount, expdest); @@ -5953,7 +6262,7 @@ subevalvar(char *p, char *str, int strloc, int subtype, rmesc = startp; rmescend = (char *)stackblock() + strloc; if (quotes) { - rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); + rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); if (rmesc != startp) { rmescend = expdest; startp = (char *)stackblock() + startloc; @@ -5968,7 +6277,7 @@ subevalvar(char *p, char *str, int strloc, int subtype, if (subtype == VSREPLACE || subtype == VSREPLACEALL) { char *idx, *end, *restart_detect; - if(!repl) { + if (!repl) { repl = parse_sub_pattern(str, varflags & VSQUOTE); if (!repl) repl = &null; @@ -6007,8 +6316,9 @@ subevalvar(char *p, char *str, int strloc, int subtype, idx++; rmesc++; } - } else + } else { idx = loc; + } for (loc = repl; *loc; loc++) { restart_detect = stackblock(); @@ -6069,12 +6379,22 @@ subevalvar(char *p, char *str, int strloc, int subtype, /* * Add the value of a specialized variable to the stack string. - */ -static ssize_t + * name parameter (examples): + * ash -c 'echo $1' name:'1=' + * ash -c 'echo $qwe' name:'qwe=' + * ash -c 'echo $$' name:'$=' + * ash -c 'echo ${$}' name:'$=' + * ash -c 'echo ${$##q}' name:'$=q' + * ash -c 'echo ${#$}' name:'$=' + * note: examples with bad shell syntax: + * ash -c 'echo ${#$1}' name:'$=1' + * ash -c 'echo ${#1#}' name:'1=#' + */ +static NOINLINE ssize_t varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) { int num; - char *p; + const char *p; int i; int sep = 0; int sepq = 0; @@ -6083,7 +6403,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) int syntax; int quoted = varflags & VSQUOTE; int subtype = varflags & VSTYPE; - int quotes = flags & (EXP_FULL | EXP_CASE); + int quotes = flags & (EXP_FULL | EXP_CASE | EXP_REDIR); if (quoted && (flags & EXP_FULL)) sep = 1 << CHAR_BIT; @@ -6105,16 +6425,21 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) return -1; numvar: len = cvtnum(num); - break; + goto check_1char_name; case '-': - p = makestrspace(NOPTS, expdest); + expdest = makestrspace(NOPTS, expdest); for (i = NOPTS - 1; i >= 0; i--) { if (optlist[i]) { - USTPUTC(optletters(i), p); + USTPUTC(optletters(i), expdest); len++; } } - expdest = p; + check_1char_name: +#if 0 + /* handles cases similar to ${#$1} */ + if (name[2] != '\0') + raise_error_syntax("bad substitution"); +#endif break; case '@': if (sep) @@ -6128,7 +6453,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) ap = shellparam.p; if (!ap) return -1; - while ((p = *ap++)) { + while ((p = *ap++) != NULL) { size_t partlen; partlen = strlen(p); @@ -6162,8 +6487,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) case '7': case '8': case '9': -// TODO: number() instead? It does error checking... - num = atoi(name); + num = atoi(name); /* number(name) fails on ${N#str} etc */ if (num < 0 || num > shellparam.nparam) return -1; p = num ? shellparam.p[num - 1] : arg0; @@ -6215,7 +6539,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) * input string. */ static char * -evalvar(char *p, int flag, struct strlist *var_str_list) +evalvar(char *p, int flags, struct strlist *var_str_list) { char varflags; char subtype; @@ -6226,7 +6550,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) int startloc; ssize_t varlen; - varflags = *p++; + varflags = (unsigned char) *p++; subtype = varflags & VSTYPE; quoted = varflags & VSQUOTE; var = p; @@ -6235,7 +6559,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) p = strchr(p, '=') + 1; again: - varlen = varvalue(var, varflags, flag, var_str_list); + varlen = varvalue(var, varflags, flags, var_str_list); if (varflags & VSNUL) varlen--; @@ -6248,8 +6572,8 @@ evalvar(char *p, int flag, struct strlist *var_str_list) vsplus: if (varlen < 0) { argstr( - p, flag | EXP_TILDE | - (quoted ? EXP_QWORD : EXP_WORD), + p, flags | EXP_TILDE | + (quoted ? EXP_QWORD : EXP_WORD), var_str_list ); goto end; @@ -6321,7 +6645,8 @@ evalvar(char *p, int flag, struct strlist *var_str_list) patloc = expdest - (char *)stackblock(); if (0 == subevalvar(p, /* str: */ NULL, patloc, subtype, startloc, varflags, - /* quotes: */ flag & (EXP_FULL | EXP_CASE), +//TODO: | EXP_REDIR too? All other such places do it too + /* quotes: */ flags & (EXP_FULL | EXP_CASE), var_str_list) ) { int amount = expdest - ( @@ -6575,7 +6900,7 @@ expmeta(char *enddir, char *name) p++; if (*p == '.') matchdot++; - while (!intpending && (dp = readdir(dirp)) != NULL) { + while (!pending_int && (dp = readdir(dirp)) != NULL) { if (dp->d_name[0] == '.' && !matchdot) continue; if (pmatch(start, dp->d_name)) { @@ -6696,7 +7021,7 @@ expandmeta(struct strlist *str /*, int flag*/) */ nometa: *exparg.lastp = str; - rmescapes(str->text); + rmescapes(str->text, 0); exparg.lastp = &str->next; } else { *exparg.lastp = NULL; @@ -6744,7 +7069,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag) expandmeta(exparg.list /*, flag*/); } else { if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ - rmescapes(p); + rmescapes(p, 0); sp = stzalloc(sizeof(*sp)); sp->text = p; *exparg.lastp = sp; @@ -6805,7 +7130,7 @@ casematch(union node *pattern, char *val) struct builtincmd { const char *name; - int (*builtin)(int, char **); + int (*builtin)(int, char **) FAST_FUNC; /* unsigned flags; */ }; #define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1) @@ -6870,14 +7195,17 @@ static int builtinloc = -1; /* index in path of %builtin, or -1 */ static void -tryexec(USE_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp) +tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp) { int repeated = 0; #if ENABLE_FEATURE_SH_STANDALONE if (applet_no >= 0) { - if (APPLET_IS_NOEXEC(applet_no)) + if (APPLET_IS_NOEXEC(applet_no)) { + while (*envp) + putenv(*envp++); run_applet_no_and_exit(applet_no, argv); + } /* re-exec ourselves with the new arguments */ execve(bb_busybox_exec_path, argv, envp); /* If they called chroot or otherwise made the binary no longer @@ -6932,20 +7260,20 @@ shellexec(char **argv, const char *path, int idx) int applet_no = -1; #endif - clearredir(1); + clearredir(/*drop:*/ 1); envp = listvars(VEXPORT, VUNSET, 0); if (strchr(argv[0], '/') != NULL #if ENABLE_FEATURE_SH_STANDALONE || (applet_no = find_applet_by_name(argv[0])) >= 0 #endif ) { - tryexec(USE_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp); + tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp); e = errno; } else { e = ENOENT; - while ((cmdname = padvance(&path, argv[0])) != NULL) { + while ((cmdname = path_advance(&path, argv[0])) != NULL) { if (--idx < 0 && pathopt == NULL) { - tryexec(USE_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); + tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); if (errno != ENOENT && errno != ENOTDIR) e = errno; } @@ -6966,8 +7294,8 @@ shellexec(char **argv, const char *path, int idx) break; } exitstatus = exerrno; - TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", - argv[0], e, suppressint)); + TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", + argv[0], e, suppress_int)); ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); /* NOTREACHED */ } @@ -6982,7 +7310,7 @@ printentry(struct tblentry *cmdp) idx = cmdp->param.index; path = pathval(); do { - name = padvance(&path, cmdp->cmdname); + name = path_advance(&path, cmdp->cmdname); stunalloc(name); } while (--idx >= 0); out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); @@ -7096,7 +7424,7 @@ addcmdentry(char *name, struct cmdentry *entry) cmdp->rehash = 0; } -static int +static int FAST_FUNC hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { struct tblentry **pp; @@ -7166,7 +7494,7 @@ hashcd(void) * pathval() still returns the old value at this point. * Called with interrupts off. */ -static void +static void FAST_FUNC changepath(const char *new) { const char *old; @@ -7354,7 +7682,7 @@ describe_command(char *command, int describe_command_verbose) p = command; } else { do { - p = padvance(&path, command); + p = path_advance(&path, command); stunalloc(p); } while (--j >= 0); } @@ -7398,7 +7726,7 @@ describe_command(char *command, int describe_command_verbose) return 0; } -static int +static int FAST_FUNC typecmd(int argc UNUSED_PARAM, char **argv) { int i = 1; @@ -7417,7 +7745,7 @@ typecmd(int argc UNUSED_PARAM, char **argv) } #if ENABLE_ASH_CMDCMD -static int +static int FAST_FUNC commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { int c; @@ -7447,43 +7775,46 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* ============ eval.c */ -static int funcblocksize; /* size of structures in function */ -static int funcstringsize; /* size of strings in node */ -static void *funcblock; /* block to allocate function from */ -static char *funcstring; /* block to allocate strings from */ +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static void *funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ /* flags in argument to evaltree */ -#define EV_EXIT 01 /* exit after evaluating tree */ -#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ #define EV_BACKCMD 04 /* command executing within back quotes */ -static const short nodesize[26] = { - SHELL_ALIGN(sizeof(struct ncmd)), - SHELL_ALIGN(sizeof(struct npipe)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nif)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nfor)), - SHELL_ALIGN(sizeof(struct ncase)), - SHELL_ALIGN(sizeof(struct nclist)), - SHELL_ALIGN(sizeof(struct narg)), - SHELL_ALIGN(sizeof(struct narg)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct ndup)), - SHELL_ALIGN(sizeof(struct ndup)), - SHELL_ALIGN(sizeof(struct nhere)), - SHELL_ALIGN(sizeof(struct nhere)), - SHELL_ALIGN(sizeof(struct nnot)), +static const uint8_t nodesize[N_NUMBER] = { + [NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)), + [NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)), + [NREDIR ] = SHELL_ALIGN(sizeof(struct nredir)), + [NBACKGND ] = SHELL_ALIGN(sizeof(struct nredir)), + [NSUBSHELL] = SHELL_ALIGN(sizeof(struct nredir)), + [NAND ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NOR ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NSEMI ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NIF ] = SHELL_ALIGN(sizeof(struct nif)), + [NWHILE ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NUNTIL ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NFOR ] = SHELL_ALIGN(sizeof(struct nfor)), + [NCASE ] = SHELL_ALIGN(sizeof(struct ncase)), + [NCLIST ] = SHELL_ALIGN(sizeof(struct nclist)), + [NDEFUN ] = SHELL_ALIGN(sizeof(struct narg)), + [NARG ] = SHELL_ALIGN(sizeof(struct narg)), + [NTO ] = SHELL_ALIGN(sizeof(struct nfile)), +#if ENABLE_ASH_BASH_COMPAT + [NTO2 ] = SHELL_ALIGN(sizeof(struct nfile)), +#endif + [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)), + [NFROM ] = SHELL_ALIGN(sizeof(struct nfile)), + [NFROMTO ] = SHELL_ALIGN(sizeof(struct nfile)), + [NAPPEND ] = SHELL_ALIGN(sizeof(struct nfile)), + [NTOFD ] = SHELL_ALIGN(sizeof(struct ndup)), + [NFROMFD ] = SHELL_ALIGN(sizeof(struct ndup)), + [NHERE ] = SHELL_ALIGN(sizeof(struct nhere)), + [NXHERE ] = SHELL_ALIGN(sizeof(struct nhere)), + [NNOT ] = SHELL_ALIGN(sizeof(struct nnot)), }; static void calcsize(union node *n); @@ -7553,6 +7884,9 @@ calcsize(union node *n) calcsize(n->narg.next); break; case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif case NCLOBBER: case NFROM: case NFROMTO: @@ -7666,6 +8000,9 @@ copynode(union node *n) new->narg.next = copynode(n->narg.next); break; case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif case NCLOBBER: case NFROM: case NFROMTO: @@ -7731,51 +8068,64 @@ defun(char *name, union node *func) INT_ON; } -static int evalskip; /* set if we are skipping commands */ -/* reasons for skipping commands (see comment on breakcmd routine) */ +/* Reasons for skipping commands (see comment on breakcmd routine) */ #define SKIPBREAK (1 << 0) #define SKIPCONT (1 << 1) #define SKIPFUNC (1 << 2) #define SKIPFILE (1 << 3) #define SKIPEVAL (1 << 4) +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 */ -/* forward decl way out to parsing code - dotrap needs it */ +/* Forward decl way out to parsing code - dotrap needs it */ static int evalstring(char *s, int mask); -/* - * Called to execute a trap. Perhaps we should avoid entering new trap - * handlers while we are executing a trap handler. +/* Called to execute a trap. + * Single callsite - at the end of evaltree(). + * If we return non-zero, exaltree raises EXEXIT exception. + * + * Perhaps we should avoid entering new trap handlers + * while we are executing a trap handler. [is it a TODO?] */ static int dotrap(void) { - char *p; - char *q; - int i; - int savestatus; - int skip; + uint8_t *g; + int sig; + uint8_t savestatus; savestatus = exitstatus; - pendingsig = 0; + pending_sig = 0; xbarrier(); - for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { - if (!*q) + TRACE(("dotrap entered\n")); + for (sig = 1, g = gotsig; sig < NSIG; sig++, g++) { + int want_exexit; + char *t; + + if (*g == 0) + continue; + t = trap[sig]; + /* non-trapped SIGINT is handled separately by raise_interrupt, + * don't upset it by resetting gotsig[SIGINT-1] */ + if (sig == SIGINT && !t) continue; - *q = '\0'; - p = trap[i + 1]; - if (!p) + TRACE(("sig %d is active, will run handler '%s'\n", sig, t)); + *g = 0; + if (!t) continue; - skip = evalstring(p, SKIPEVAL); + want_exexit = evalstring(t, SKIPEVAL); exitstatus = savestatus; - if (skip) - return skip; + if (want_exexit) { + TRACE(("dotrap returns %d\n", want_exexit)); + return want_exexit; + } } + TRACE(("dotrap returns 0\n")); return 0; } @@ -7797,16 +8147,39 @@ static void prehash(union node *); static void evaltree(union node *n, int flags) { + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; int checkexit = 0; void (*evalfn)(union node *, int); - unsigned isor; int status; + int int_level; + + SAVE_INT(int_level); + if (n == NULL) { TRACE(("evaltree(NULL) called\n")); - goto out; + goto out1; + } + TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags)); + + exception_handler = &jmploc; + { + int err = setjmp(jmploc.loc); + if (err) { + /* if it was a signal, check for trap handlers */ + if (exception_type == EXSIG) { + TRACE(("exception %d (EXSIG) in evaltree, err=%d\n", + exception_type, err)); + goto out; + } + /* continue on the way out */ + TRACE(("exception %d in evaltree, propagating err=%d\n", + exception_type, err)); + exception_handler = savehandler; + longjmp(exception_handler->loc, err); + } } - TRACE(("pid %d, evaltree(%p: %d, %d) called\n", - getpid(), n, n->type, flags)); + switch (n->type) { default: #if DEBUG @@ -7825,7 +8198,7 @@ evaltree(union node *n, int flags) evaltree(n->nredir.n, flags & EV_TESTED); status = exitstatus; } - popredir(0); + popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); goto setstatus; case NCMD: evalfn = evalcommand; @@ -7852,19 +8225,20 @@ evaltree(union node *n, int flags) goto calleval; case NAND: case NOR: - case NSEMI: + case NSEMI: { + #if NAND + 1 != NOR #error NAND + 1 != NOR #endif #if NOR + 1 != NSEMI #error NOR + 1 != NSEMI #endif - isor = n->type - NAND; + unsigned is_or = n->type - NAND; evaltree( n->nbinary.ch1, - (flags | ((isor >> 1) - 1)) & EV_TESTED + (flags | ((is_or >> 1) - 1)) & EV_TESTED ); - if (!exitstatus == isor) + if (!exitstatus == is_or) break; if (!evalskip) { n = n->nbinary.ch2; @@ -7875,6 +8249,7 @@ evaltree(union node *n, int flags) break; } break; + } case NIF: evaltree(n->nif.test, EV_TESTED); if (evalskip) @@ -7882,7 +8257,8 @@ evaltree(union node *n, int flags) if (exitstatus == 0) { n = n->nif.ifpart; goto evaln; - } else if (n->nif.elsepart) { + } + if (n->nif.elsepart) { n = n->nif.elsepart; goto evaln; } @@ -7895,16 +8271,22 @@ evaltree(union node *n, int flags) exitstatus = status; break; } + out: - if ((checkexit & exitstatus)) + exception_handler = savehandler; + out1: + if (checkexit & exitstatus) evalskip |= SKIPEVAL; - else if (pendingsig && dotrap()) + else if (pending_sig && dotrap()) goto exexit; if (flags & EV_EXIT) { exexit: raise_exception(EXEXIT); } + + RESTORE_INT(int_level); + TRACE(("leaving evaltree (no interrupts)\n")); } #if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) @@ -8065,17 +8447,33 @@ expredir(union node *n) case NFROMTO: case NFROM: case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif case NCLOBBER: case NAPPEND: expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); +#if ENABLE_ASH_BASH_COMPAT + store_expfname: +#endif redir->nfile.expfname = fn.list->text; break; case NFROMFD: - case NTOFD: + case NTOFD: /* >& */ if (redir->ndup.vname) { expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); if (fn.list == NULL) ash_msg_and_raise_error("redir error"); +#if ENABLE_ASH_BASH_COMPAT +//FIXME: we used expandarg with different args! + if (!isdigit_str9(fn.list->text)) { + /* >&file, not >&fd */ + if (redir->nfile.fd != 1) /* 123>&file - BAD */ + ash_msg_and_raise_error("redir error"); + redir->type = NTO2; + goto store_expfname; + } +#endif fixredir(redir, fn.list->text, 1); } break; @@ -8134,7 +8532,9 @@ evalpipe(union node *n, int flags) if (prevfd >= 0) close(prevfd); prevfd = pip[0]; - close(pip[1]); + /* Don't want to trigger debugging */ + if (pip[1] != -1) + close(pip[1]); } if (n->npipe.pipe_backgnd == 0) { exitstatus = waitforjob(jp); @@ -8163,12 +8563,13 @@ setinteractive(int on) static smallint did_banner; if (!did_banner) { - out1fmt( - "\n\n" - "%s built-in shell (ash)\n" + /* note: ash and hush share this string */ + out1fmt("\n\n%s %s\n" "Enter 'help' for a list of built-in commands." "\n\n", - bb_banner); + bb_banner, + "built-in shell (ash)" + ); did_banner = 1; } } @@ -8208,7 +8609,7 @@ poplocalvars(void) while ((lvp = localvars) != NULL) { localvars = lvp->next; vp = lvp->vp; - TRACE(("poplocalvar %s", vp ? vp->text : "-")); + TRACE(("poplocalvar %s\n", vp ? vp->text : "-")); if (vp == NULL) { /* $- saved */ memcpy(optlist, lvp->text, sizeof(optlist)); free((char*)lvp->text); @@ -8356,7 +8757,7 @@ mklocal(char *name) /* * The "local" command. */ -static int +static int FAST_FUNC localcmd(int argc UNUSED_PARAM, char **argv) { char *name; @@ -8368,19 +8769,19 @@ localcmd(int argc UNUSED_PARAM, char **argv) return 0; } -static int +static int FAST_FUNC falsecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { return 1; } -static int +static int FAST_FUNC truecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { return 0; } -static int +static int FAST_FUNC execcmd(int argc UNUSED_PARAM, char **argv) { if (argv[1]) { @@ -8395,7 +8796,7 @@ execcmd(int argc UNUSED_PARAM, char **argv) /* * The return command. */ -static int +static int FAST_FUNC returncmd(int argc UNUSED_PARAM, char **argv) { /* @@ -8407,28 +8808,28 @@ returncmd(int argc UNUSED_PARAM, char **argv) } /* Forward declarations for builtintab[] */ -static int breakcmd(int, char **); -static int dotcmd(int, char **); -static int evalcmd(int, char **); -static int exitcmd(int, char **); -static int exportcmd(int, char **); +static int breakcmd(int, char **) FAST_FUNC; +static int dotcmd(int, char **) FAST_FUNC; +static int evalcmd(int, char **) FAST_FUNC; +static int exitcmd(int, char **) FAST_FUNC; +static int exportcmd(int, char **) FAST_FUNC; #if ENABLE_ASH_GETOPTS -static int getoptscmd(int, char **); +static int getoptscmd(int, char **) FAST_FUNC; #endif #if !ENABLE_FEATURE_SH_EXTRA_QUIET -static int helpcmd(int, char **); +static int helpcmd(int, char **) FAST_FUNC; #endif -#if ENABLE_ASH_MATH_SUPPORT -static int letcmd(int, char **); +#if ENABLE_SH_MATH_SUPPORT +static int letcmd(int, char **) FAST_FUNC; #endif -static int readcmd(int, char **); -static int setcmd(int, char **); -static int shiftcmd(int, char **); -static int timescmd(int, char **); -static int trapcmd(int, char **); -static int umaskcmd(int, char **); -static int unsetcmd(int, char **); -static int ulimitcmd(int, char **); +static int readcmd(int, char **) FAST_FUNC; +static int setcmd(int, char **) FAST_FUNC; +static int shiftcmd(int, char **) FAST_FUNC; +static int timescmd(int, char **) FAST_FUNC; +static int trapcmd(int, char **) FAST_FUNC; +static int umaskcmd(int, char **) FAST_FUNC; +static int unsetcmd(int, char **) FAST_FUNC; +static int ulimitcmd(int, char **) FAST_FUNC; #define BUILTIN_NOSPEC "0" #define BUILTIN_SPECIAL "1" @@ -8439,21 +8840,16 @@ static int ulimitcmd(int, char **); #define BUILTIN_REG_ASSG "6" #define BUILTIN_SPEC_REG_ASSG "7" -/* We do not handle [[ expr ]] bashism bash-compatibly, - * we make it a synonym of [ expr ]. - * Basically, word splitting and pathname expansion should NOT be performed - * Examples: - * no word splitting: a="a b"; [[ $a = "a b" ]]; echo $? should print "0" - * no pathname expansion: [[ /bin/m* = "/bin/m*" ]]; echo $? should print "0" - * Additional operators: - * || and && should work as -o and -a - * =~ regexp match - * Apart from the above, [[ expr ]] should work as [ expr ] - */ - -#define echocmd echo_main -#define printfcmd printf_main -#define testcmd test_main +/* Stubs for calling non-FAST_FUNC's */ +#if ENABLE_ASH_BUILTIN_ECHO +static int FAST_FUNC echocmd(int argc, char **argv) { return echo_main(argc, argv); } +#endif +#if ENABLE_ASH_BUILTIN_PRINTF +static int FAST_FUNC printfcmd(int argc, char **argv) { return printf_main(argc, argv); } +#endif +#if ENABLE_ASH_BUILTIN_TEST +static int FAST_FUNC testcmd(int argc, char **argv) { return test_main(argc, argv); } +#endif /* Keep these in proper order since it is searched via bsearch() */ static const struct builtincmd builtintab[] = { @@ -8500,7 +8896,7 @@ static const struct builtincmd builtintab[] = { { BUILTIN_REGULAR "jobs", jobscmd }, { BUILTIN_REGULAR "kill", killcmd }, #endif -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT { BUILTIN_NOSPEC "let", letcmd }, #endif { BUILTIN_ASSIGN "local", localcmd }, @@ -8576,7 +8972,7 @@ isassignment(const char *p) return 0; return *q == '='; } -static int +static int FAST_FUNC bltincmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { /* Preserve exitstatus of a previous possible redirection @@ -8601,11 +8997,11 @@ evalcommand(union node *cmd, int flags) char *lastarg; const char *path; int spclbltin; - int cmd_is_exec; int status; char **nargv; struct builtincmd *bcmd; - int pseudovarflag = 0; + smallint cmd_is_exec; + smallint pseudovarflag = 0; /* First expand the arguments. */ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); @@ -8716,7 +9112,7 @@ evalcommand(union node *cmd, int flags) if (spclbltin < 0) spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); if (cmdentry.u.cmd == EXECCMD) - cmd_is_exec++; + cmd_is_exec = 1; #if ENABLE_ASH_CMDCMD if (cmdentry.u.cmd == COMMANDCMD) { path = oldpath; @@ -8744,7 +9140,10 @@ evalcommand(union node *cmd, int flags) /* Execute the command. */ switch (cmdentry.cmdtype) { default: + #if ENABLE_FEATURE_SH_NOFORK +/* Hmmm... shouldn't it happen somewhere in forkshell() instead? + * Why "fork off a child process if necessary" doesn't apply to NOFORK? */ { /* find_command() encodes applet_no as (-2 - applet_no) */ int applet_no = (- cmdentry.u.index - 2); @@ -8756,7 +9155,6 @@ evalcommand(union node *cmd, int flags) } } #endif - /* Fork off a child process if necessary. */ if (!(flags & EV_EXIT) || trap[0]) { INT_OFF; @@ -8764,6 +9162,7 @@ evalcommand(union node *cmd, int flags) if (forkshell(jp, cmd, FORK_FG) != 0) { exitstatus = waitforjob(jp); INT_ON; + TRACE(("forked child exited with %d\n", exitstatus)); break; } FORCE_INT_ON; @@ -8784,16 +9183,22 @@ evalcommand(union node *cmd, int flags) } listsetvar(list, i); } + /* 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)) { int exit_status; - int i = exception; + int i = exception_type; if (i == EXEXIT) goto raise; exit_status = 2; if (i == EXINT) exit_status = 128 + SIGINT; if (i == EXSIG) - exit_status = 128 + pendingsig; + exit_status = 128 + pending_sig; exitstatus = exit_status; if (i == EXINT || spclbltin > 0) { raise: @@ -8805,13 +9210,15 @@ evalcommand(union node *cmd, int flags) case CMDFUNCTION: listsetvar(varlist.list, 0); + /* See above for the rationale */ + dowait(DOWAIT_NONBLOCK, NULL); if (evalfun(cmdentry.u.func, argc, argv, flags)) goto raise; break; } out: - popredir(cmd_is_exec); + popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); if (lastarg) { /* dsl: I think this is intended to be used to support * '_' in 'vi' command mode during line editing... @@ -8845,7 +9252,6 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv) exitstatus |= ferror(stdout); clearerr(stdout); commandname = savecmdname; -// exsig = 0; exception_handler = savehandler; return i; @@ -8890,13 +9296,13 @@ prehash(union node *n) * be an error to break out of more loops than exist, but it isn't * in the standard shell so we don't make it one here. */ -static int +static int FAST_FUNC breakcmd(int argc UNUSED_PARAM, char **argv) { int n = argv[1] ? number(argv[1]) : 1; if (n <= 0) - ash_msg_and_raise_error(illnum, argv[1]); + ash_msg_and_raise_error(msg_illnum, argv[1]); if (n > loopnest) n = loopnest; if (n > 0) { @@ -8912,39 +9318,68 @@ breakcmd(int argc UNUSED_PARAM, char **argv) * This implements the input routines used by the parser. */ -#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ - enum { INPUT_PUSH_FILE = 1, INPUT_NOFILE_OK = 2, }; -static int plinno = 1; /* input line number */ -/* number of characters left in input buffer */ -static int parsenleft; /* copy of parsefile->nleft */ -static int parselleft; /* copy of parsefile->lleft */ -/* next character in input buffer */ -static char *parsenextc; /* copy of parsefile->nextc */ - static smallint checkkwd; /* values of checkkwd variable */ #define CHKALIAS 0x1 #define CHKKWD 0x2 #define CHKNL 0x4 +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +#if !ENABLE_ASH_ALIAS +#define pushstring(s, ap) pushstring(s) +#endif static void -popstring(void) +pushstring(char *s, struct alias *ap) { - struct strpush *sp = g_parsefile->strpush; + struct strpush *sp; + int len; + len = strlen(s); INT_OFF; + if (g_parsefile->strpush) { + sp = ckzalloc(sizeof(*sp)); + sp->prev = g_parsefile->strpush; + } else { + sp = &(g_parsefile->basestrpush); + } + g_parsefile->strpush = sp; + sp->prev_string = g_parsefile->next_to_pgetc; + sp->prev_left_in_line = g_parsefile->left_in_line; #if ENABLE_ASH_ALIAS - if (sp->ap) { - if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') { - checkkwd |= CHKALIAS; - } - if (sp->string != sp->ap->val) { - free(sp->string); + sp->ap = ap; + if (ap) { + ap->flag |= ALIASINUSE; + sp->string = s; + } +#endif + g_parsefile->next_to_pgetc = s; + g_parsefile->left_in_line = len; + INT_ON; +} + +static void +popstring(void) +{ + struct strpush *sp = g_parsefile->strpush; + + INT_OFF; +#if ENABLE_ASH_ALIAS + if (sp->ap) { + if (g_parsefile->next_to_pgetc[-1] == ' ' + || g_parsefile->next_to_pgetc[-1] == '\t' + ) { + checkkwd |= CHKALIAS; + } + if (sp->string != sp->ap->val) { + free(sp->string); } sp->ap->flag &= ~ALIASINUSE; if (sp->ap->flag & ALIASDEAD) { @@ -8952,25 +9387,29 @@ popstring(void) } } #endif - parsenextc = sp->prevstring; - parsenleft = sp->prevnleft; -/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + g_parsefile->next_to_pgetc = sp->prev_string; + g_parsefile->left_in_line = sp->prev_left_in_line; g_parsefile->strpush = sp->prev; if (sp != &(g_parsefile->basestrpush)) free(sp); INT_ON; } +//FIXME: BASH_COMPAT with "...&" does TWO pungetc(): +//it peeks whether it is &>, and then pushes back both chars. +//This function needs to save last *next_to_pgetc to buf[0] +//to make two pungetc() reliable. Currently, +// pgetc (out of buf: does preadfd), pgetc, pungetc, pungetc won't work... static int preadfd(void) { int nr; - char *buf = g_parsefile->buf; - parsenextc = buf; + char *buf = g_parsefile->buf; + g_parsefile->next_to_pgetc = buf; #if ENABLE_FEATURE_EDITING retry: - if (!iflag || g_parsefile->fd) + if (!iflag || g_parsefile->fd != STDIN_FILENO) nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1); else { #if ENABLE_FEATURE_TAB_COMPLETION @@ -9018,94 +9457,143 @@ preadfd(void) * Refill the input buffer and return the next input character: * * 1) If a string was pushed back on the input, pop it; - * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading - * from a string so we can't refill the buffer, return EOF. - * 3) If the is more stuff in this buffer, use it else call read to fill it. + * 2) If an EOF was pushed back (g_parsefile->left_in_line < -BIGNUM) + * or we are reading from a string so we can't refill the buffer, + * return EOF. + * 3) If there is more stuff in this buffer, use it else call read to fill it. * 4) Process input up to the next newline, deleting nul characters. */ +//#define pgetc_debug(...) bb_error_msg(__VA_ARGS__) +#define pgetc_debug(...) ((void)0) +/* + * NB: due to SIT(c) internals (syntax_index_table[] vector), + * pgetc() and related functions must return chars SIGN-EXTENDED into ints, + * not zero-extended. Seems fragile to me. Affects only !USE_SIT_FUNCTION case, + * so we can fix it by ditching !USE_SIT_FUNCTION if Unicode requires that. + */ static int preadbuffer(void) { char *q; int more; - char savec; while (g_parsefile->strpush) { #if ENABLE_ASH_ALIAS - if (parsenleft == -1 && g_parsefile->strpush->ap && - parsenextc[-1] != ' ' && parsenextc[-1] != '\t') { + if (g_parsefile->left_in_line == -1 + && g_parsefile->strpush->ap + && g_parsefile->next_to_pgetc[-1] != ' ' + && g_parsefile->next_to_pgetc[-1] != '\t' + ) { + pgetc_debug("preadbuffer PEOA"); return PEOA; } #endif popstring(); - if (--parsenleft >= 0) - return signed_char2int(*parsenextc++); - } - if (parsenleft == EOF_NLEFT || g_parsefile->buf == NULL) + /* try "pgetc" now: */ + pgetc_debug("preadbuffer internal pgetc at %d:%p'%s'", + g_parsefile->left_in_line, + g_parsefile->next_to_pgetc, + g_parsefile->next_to_pgetc); + if (--g_parsefile->left_in_line >= 0) + return (unsigned char)(*g_parsefile->next_to_pgetc++); + } + /* on both branches above g_parsefile->left_in_line < 0. + * "pgetc" needs refilling. + */ + + /* -90 is our -BIGNUM. Below we use -99 to mark "EOF on read", + * pungetc() may increment it a few times. + * Assuming it won't increment it to less than -90. + */ + if (g_parsefile->left_in_line < -90 || g_parsefile->buf == NULL) { + pgetc_debug("preadbuffer PEOF1"); + /* even in failure keep left_in_line and next_to_pgetc + * in lock step, for correct multi-layer pungetc. + * left_in_line was decremented before preadbuffer(), + * must inc next_to_pgetc: */ + g_parsefile->next_to_pgetc++; return PEOF; - flush_stdout_stderr(); + } - more = parselleft; + more = g_parsefile->left_in_buffer; if (more <= 0) { + flush_stdout_stderr(); again: more = preadfd(); if (more <= 0) { - parselleft = parsenleft = EOF_NLEFT; + /* don't try reading again */ + g_parsefile->left_in_line = -99; + pgetc_debug("preadbuffer PEOF2"); + g_parsefile->next_to_pgetc++; return PEOF; } } - q = parsenextc; - - /* delete nul characters */ + /* Find out where's the end of line. + * Set g_parsefile->left_in_line + * and g_parsefile->left_in_buffer acordingly. + * NUL chars are deleted. + */ + q = g_parsefile->next_to_pgetc; for (;;) { - int c; + char c; more--; - c = *q; - if (!c) + c = *q; + if (c == '\0') { memmove(q, q + 1, more); - else { + } else { q++; if (c == '\n') { - parsenleft = q - parsenextc - 1; + g_parsefile->left_in_line = q - g_parsefile->next_to_pgetc - 1; break; } } if (more <= 0) { - parsenleft = q - parsenextc - 1; - if (parsenleft < 0) + g_parsefile->left_in_line = q - g_parsefile->next_to_pgetc - 1; + if (g_parsefile->left_in_line < 0) goto again; break; } } - parselleft = more; - - savec = *q; - *q = '\0'; + g_parsefile->left_in_buffer = more; if (vflag) { - out2str(parsenextc); + char save = *q; + *q = '\0'; + out2str(g_parsefile->next_to_pgetc); + *q = save; } - *q = savec; - - return signed_char2int(*parsenextc++); + pgetc_debug("preadbuffer at %d:%p'%s'", + g_parsefile->left_in_line, + g_parsefile->next_to_pgetc, + g_parsefile->next_to_pgetc); + return signed_char2int(*g_parsefile->next_to_pgetc++); } -#define pgetc_as_macro() (--parsenleft >= 0? signed_char2int(*parsenextc++) : preadbuffer()) +#define pgetc_as_macro() \ + (--g_parsefile->left_in_line >= 0 \ + ? signed_char2int(*g_parsefile->next_to_pgetc++) \ + : preadbuffer() \ + ) + static int pgetc(void) { + pgetc_debug("pgetc_fast at %d:%p'%s'", + g_parsefile->left_in_line, + g_parsefile->next_to_pgetc, + g_parsefile->next_to_pgetc); return pgetc_as_macro(); } #if ENABLE_ASH_OPTIMIZE_FOR_SIZE -#define pgetc_macro() pgetc() +#define pgetc_fast() pgetc() #else -#define pgetc_macro() pgetc_as_macro() +#define pgetc_fast() pgetc_as_macro() #endif /* @@ -9116,18 +9604,17 @@ static int pgetc2(void) { int c; - do { - c = pgetc_macro(); + pgetc_debug("pgetc_fast at %d:%p'%s'", + g_parsefile->left_in_line, + g_parsefile->next_to_pgetc, + g_parsefile->next_to_pgetc); + c = pgetc_fast(); } while (c == PEOA); return c; } #else -static int -pgetc2(void) -{ - return pgetc_macro(); -} +#define pgetc2() pgetc() #endif /* @@ -9162,44 +9649,12 @@ pfgets(char *line, int len) static void pungetc(void) { - parsenleft++; - parsenextc--; -} - -/* - * Push a string back onto the input at this current parsefile level. - * We handle aliases this way. - */ -#if !ENABLE_ASH_ALIAS -#define pushstring(s, ap) pushstring(s) -#endif -static void -pushstring(char *s, struct alias *ap) -{ - struct strpush *sp; - size_t len; - - len = strlen(s); - INT_OFF; -/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/ - if (g_parsefile->strpush) { - sp = ckzalloc(sizeof(struct strpush)); - sp->prev = g_parsefile->strpush; - g_parsefile->strpush = sp; - } else - sp = g_parsefile->strpush = &(g_parsefile->basestrpush); - sp->prevstring = parsenextc; - sp->prevnleft = parsenleft; -#if ENABLE_ASH_ALIAS - sp->ap = ap; - if (ap) { - ap->flag |= ALIASINUSE; - sp->string = s; - } -#endif - parsenextc = s; - parsenleft = len; - INT_ON; + g_parsefile->left_in_line++; + g_parsefile->next_to_pgetc--; + pgetc_debug("pushed back to %d:%p'%s'", + g_parsefile->left_in_line, + g_parsefile->next_to_pgetc, + g_parsefile->next_to_pgetc); } /* @@ -9211,10 +9666,6 @@ pushfile(void) { struct parsefile *pf; - g_parsefile->nleft = parsenleft; - g_parsefile->lleft = parselleft; - g_parsefile->nextc = parsenextc; - g_parsefile->linno = plinno; pf = ckzalloc(sizeof(*pf)); pf->prev = g_parsefile; pf->fd = -1; @@ -9236,10 +9687,6 @@ popfile(void) popstring(); g_parsefile = pf->prev; free(pf); - parsenleft = g_parsefile->nleft; - parselleft = g_parsefile->lleft; - parsenextc = g_parsefile->nextc; - plinno = g_parsefile->linno; INT_ON; } @@ -9277,13 +9724,14 @@ setinputfd(int fd, int push) close_on_exec_on(fd); if (push) { pushfile(); - g_parsefile->buf = 0; + g_parsefile->buf = NULL; } g_parsefile->fd = fd; if (g_parsefile->buf == NULL) g_parsefile->buf = ckmalloc(IBUFSIZ); - parselleft = parsenleft = 0; - plinno = 1; + g_parsefile->left_in_buffer = 0; + g_parsefile->left_in_line = 0; + g_parsefile->linno = 1; } /* @@ -9301,7 +9749,7 @@ setinputfile(const char *fname, int flags) if (fd < 0) { if (flags & INPUT_NOFILE_OK) goto out; - ash_msg_and_raise_error("can't open %s", fname); + ash_msg_and_raise_error("can't open '%s'", fname); } if (fd < 10) { fd2 = copyfd(fd, 10); @@ -9324,10 +9772,10 @@ setinputstring(char *string) { INT_OFF; pushfile(); - parsenextc = string; - parsenleft = strlen(string); + g_parsefile->next_to_pgetc = string; + g_parsefile->left_in_line = strlen(string); g_parsefile->buf = NULL; - plinno = 1; + g_parsefile->linno = 1; INT_ON; } @@ -9365,7 +9813,7 @@ chkmail(void) setstackmark(&smark); mpath = mpathset() ? mpathval() : mailval(); for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { - p = padvance(&mpath, nullstr); + p = path_advance(&mpath, nullstr); if (p == NULL) break; if (*p == '\0') @@ -9393,7 +9841,7 @@ chkmail(void) popstackmark(&smark); } -static void +static void FAST_FUNC changemail(const char *val UNUSED_PARAM) { mail_var_path_changed = 1; @@ -9549,7 +9997,7 @@ options(int cmdline) /* * The shift builtin command. */ -static int +static int FAST_FUNC shiftcmd(int argc UNUSED_PARAM, char **argv) { int n; @@ -9559,7 +10007,7 @@ shiftcmd(int argc UNUSED_PARAM, char **argv) if (argv[1]) n = number(argv[1]); if (n > shellparam.nparam) - n = shellparam.nparam; + n = 0; /* bash compat, was = shellparam.nparam; */ INT_OFF; shellparam.nparam -= n; for (ap1 = shellparam.p; --n >= 0; ap1++) { @@ -9611,7 +10059,7 @@ showvars(const char *sep_prefix, int on, int off) /* * The set command builtin. */ -static int +static int FAST_FUNC setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { int retval; @@ -9632,37 +10080,21 @@ setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) } #if ENABLE_ASH_RANDOM_SUPPORT -static void +static void FAST_FUNC change_random(const char *value) { - /* Galois LFSR parameter */ - /* Taps at 32 31 29 1: */ - enum { MASK = 0x8000000b }; - /* Another example - taps at 32 31 30 10: */ - /* MASK = 0x00400007 */ + uint32_t t; if (value == NULL) { /* "get", generate */ - uint32_t t; - - /* LCG has period of 2^32 and alternating lowest bit */ - random_LCG = 1664525 * random_LCG + 1013904223; - /* Galois LFSR has period of 2^32-1 = 3 * 5 * 17 * 257 * 65537 */ - t = (random_galois_LFSR << 1); - if (random_galois_LFSR < 0) /* if we just shifted 1 out of msb... */ - t ^= MASK; - random_galois_LFSR = t; - /* Both are weak, combining them gives better randomness - * and ~2^64 period. & 0x7fff is probably bash compat - * for $RANDOM range. Combining with subtraction is - * just for fun. + and ^ would work equally well. */ - t = (t - random_LCG) & 0x7fff; + t = next_random(&random_gen); /* set without recursion */ setvar(vrandom.text, utoa(t), VNOFUNC); vrandom.flags &= ~VNOFUNC; } else { /* set/reset */ - random_galois_LFSR = random_LCG = strtoul(value, (char **)NULL, 10); + t = strtoul(value, NULL, 10); + INIT_RANDOM_T(&random_gen, (t ? t : 1), t); } } #endif @@ -9762,7 +10194,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt * be processed in the current argument. If shellparam.optnext is NULL, * then it's the first time getopts has been called. */ -static int +static int FAST_FUNC getoptscmd(int argc, char **argv) { char **optbase; @@ -9807,20 +10239,6 @@ static char *wordtext; /* text of last word returned by readtoke static struct nodelist *backquotelist; static union node *redirnode; static struct heredoc *heredoc; -/* - * NEOF is returned by parsecmd when it encounters an end of file. It - * must be distinct from NULL, so we use the address of a variable that - * happens to be handy. - */ -#define NEOF ((union node *)&tokpushback) - -static void raise_error_syntax(const char *) NORETURN; -static void -raise_error_syntax(const char *msg) -{ - ash_msg_and_raise_error("syntax error: %s", msg); - /* NOTREACHED */ -} /* * Called when an unexpected token is read during the parse. The argument @@ -9834,7 +10252,7 @@ raise_error_unexpected_syntax(int token) char msg[64]; int l; - l = sprintf(msg, "%s unexpected", tokname(lasttoken)); + l = sprintf(msg, "unexpected %s", tokname(lasttoken)); if (token >= 0) sprintf(msg + l, " (expecting %s)", tokname(token)); raise_error_syntax(msg); @@ -10003,17 +10421,20 @@ makename(void) static void fixredir(union node *n, const char *text, int err) { + int fd; + TRACE(("Fix redir %s %d\n", text, err)); if (!err) n->ndup.vname = NULL; - if (isdigit(text[0]) && text[1] == '\0') - n->ndup.dupfd = text[0] - '0'; + fd = bb_strtou(text, NULL, 10); + if (!errno && fd >= 0) + n->ndup.dupfd = fd; else if (LONE_DASH(text)) n->ndup.dupfd = -1; else { if (err) - raise_error_syntax("Bad fd number"); + raise_error_syntax("bad fd number"); n->ndup.vname = makename(); } } @@ -10023,9 +10444,9 @@ fixredir(union node *n, const char *text, int err) * or backquotes). */ static int -noexpand(char *text) +noexpand(const char *text) { - char *p; + const char *p; char c; p = text; @@ -10034,7 +10455,7 @@ noexpand(char *text) continue; if (c == CTLESC) p++; - else if (SIT(c, BASESYNTAX) == CCTL) + else if (SIT((signed char)c, BASESYNTAX) == CCTL) return 0; } return 1; @@ -10056,8 +10477,8 @@ parsefname(void) n->type = NXHERE; TRACE(("Here document %d\n", n->type)); if (!noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN) - raise_error_syntax("Illegal eof marker for << redirection"); - rmescapes(wordtext); + raise_error_syntax("illegal eof marker for << redirection"); + rmescapes(wordtext, 0); here->eofmark = wordtext; here->next = NULL; if (heredoclist == NULL) @@ -10148,7 +10569,7 @@ simplecmd(void) if (!goodname(name) || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) ) { - raise_error_syntax("Bad function name"); + raise_error_syntax("bad function name"); } n->type = NDEFUN; checkkwd = CHKNL | CHKKWD | CHKALIAS; @@ -10233,7 +10654,7 @@ parse_command(void) } case TFOR: if (readtoken() != TWORD || quoteflag || !goodname(wordtext)) - raise_error_syntax("Bad for loop variable"); + raise_error_syntax("bad for loop variable"); n1 = stzalloc(sizeof(struct nfor)); n1->type = NFOR; n1->nfor.var = wordtext; @@ -10453,7 +10874,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) int parenlevel; /* levels of parens in arithmetic */ int dqvarnest; /* levels of variables expansion within double quotes */ - USE_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;) + IF_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;) #if __GNUC__ /* Avoid longjmp clobbering */ @@ -10468,7 +10889,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) (void) &prevsyntax; (void) &syntax; #endif - startlinno = plinno; + startlinno = g_parsefile->linno; bqlist = NULL; quotef = 0; oldstyle = 0; @@ -10485,7 +10906,9 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) dqvarnest = 0; STARTSTACKSTR(out); - loop: { /* for each line, until end of word */ + loop: + /* For each line, until end of word */ + { 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 */ @@ -10494,7 +10917,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) if (syntax == BASESYNTAX) goto endword; /* exit outer loop */ USTPUTC(c, out); - plinno++; + g_parsefile->linno++; if (doprompt) setprompt(2); c = pgetc(); @@ -10557,7 +10980,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) dblquote = 1; goto quotemark; case CENDQUOTE: - USE_ASH_BASH_COMPAT(bash_dollar_squote = 0;) + IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;) if (eofmark != NULL && arinest == 0 && varnest == 0 ) { @@ -10585,7 +11008,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) USTPUTC(c, out); } break; -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT case CLP: /* '(' in arithmetic */ parenlevel++; USTPUTC(c, out); @@ -10621,41 +11044,51 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) case CIGN: break; default: - if (varnest == 0) + if (varnest == 0) { +#if ENABLE_ASH_BASH_COMPAT + if (c == '&') { + if (pgetc() == '>') + c = 0x100 + '>'; /* flag &> */ + pungetc(); + } +#endif goto endword; /* exit outer loop */ + } #if ENABLE_ASH_ALIAS if (c != PEOA) #endif USTPUTC(c, out); } - c = pgetc_macro(); - } /* for(;;) */ + c = pgetc_fast(); + } /* for (;;) */ } endword: -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT if (syntax == ARISYNTAX) - raise_error_syntax("Missing '))'"); + raise_error_syntax("missing '))'"); #endif if (syntax != BASESYNTAX && !parsebackquote && eofmark == NULL) - raise_error_syntax("Unterminated quoted string"); + raise_error_syntax("unterminated quoted string"); if (varnest != 0) { - startlinno = plinno; + startlinno = g_parsefile->linno; /* { */ - raise_error_syntax("Missing '}'"); + raise_error_syntax("missing '}'"); } USTPUTC('\0', out); len = out - (char *)stackblock(); out = stackblock(); if (eofmark == NULL) { - if ((c == '>' || c == '<') + if ((c == '>' || c == '<' IF_ASH_BASH_COMPAT( || c == 0x100 + '>')) && quotef == 0 - && len <= 2 // THIS LIMITS fd to 1 char: N>file, but no NN>file! - && (*out == '\0' || isdigit(*out)) ) { - PARSEREDIR(); - lasttoken = TREDIR; - return lasttoken; + if (isdigit_str9(out)) { + PARSEREDIR(); /* passed as params: out, c */ + lasttoken = TREDIR; + return lasttoken; + } + /* else: non-number X seen, interpret it + * as "NNNX>file" = "NNNX >file" */ } pungetc(); } @@ -10693,7 +11126,7 @@ checkend: { continue; if (*p == '\n' && *q == '\0') { c = PEOF; - plinno++; + g_parsefile->linno++; needprompt = doprompt; } else { pushstring(line, NULL); @@ -10710,7 +11143,8 @@ checkend: { * first character of the redirection operator. */ parseredir: { - char fd = *out; + /* out is already checked to be a valid number or "" */ + int fd = (*out == '\0' ? -1 : atoi(out)); union node *np; np = stzalloc(sizeof(struct nfile)); @@ -10723,11 +11157,20 @@ parseredir: { np->type = NCLOBBER; else if (c == '&') np->type = NTOFD; + /* it also can be NTO2 (>&file), but we can't figure it out yet */ else { np->type = NTO; pungetc(); } - } else { /* c == '<' */ + } +#if ENABLE_ASH_BASH_COMPAT + else if (c == 0x100 + '>') { /* this flags &> redirection */ + np->nfile.fd = 1; + pgetc(); /* this is '>', no need to check */ + np->type = NTO2; + } +#endif + else { /* c == '<' */ /*np->nfile.fd = 0; - stzalloc did it */ c = pgetc(); switch (c) { @@ -10762,8 +11205,8 @@ parseredir: { break; } } - if (fd != '\0') - np->nfile.fd = fd - '0'; + if (fd >= 0) + np->nfile.fd = fd; redirnode = np; goto parseredir_return; } @@ -10798,7 +11241,7 @@ parsesub: { pungetc(); } else if (c == '(') { /* $(command) or $((arith)) */ if (pgetc() == '(') { -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT PARSEARITH(); #else raise_error_syntax("you disabled math support for $((arith)) syntax"); @@ -10836,8 +11279,12 @@ parsesub: { } else if (is_special(c)) { USTPUTC(c, out); c = pgetc(); - } else - badsub: raise_error_syntax("Bad substitution"); + } else { + badsub: + raise_error_syntax("bad substitution"); + } + if (c != '}' && subtype == VSLENGTH) + goto badsub; STPUTC('=', out); flags = 0; @@ -10957,7 +11404,7 @@ parsebackq: { case '\\': pc = pgetc(); if (pc == '\n') { - plinno++; + g_parsefile->linno++; if (doprompt) setprompt(2); /* @@ -10980,11 +11427,11 @@ parsebackq: { #if ENABLE_ASH_ALIAS case PEOA: #endif - startlinno = plinno; + startlinno = g_parsefile->linno; raise_error_syntax("EOF in backquote substitution"); case '\n': - plinno++; + g_parsefile->linno++; needprompt = doprompt; break; @@ -11051,7 +11498,7 @@ parsebackq: { goto parsebackq_newreturn; } -#if ENABLE_ASH_MATH_SUPPORT +#if ENABLE_SH_MATH_SUPPORT /* * Parse an arithmetic expansion (indicate start of one and set state) */ @@ -11098,9 +11545,14 @@ parsearith: { #ifdef NEW_xxreadtoken /* singles must be first! */ static const char xxreadtoken_chars[7] ALIGN1 = { - '\n', '(', ')', '&', '|', ';', 0 + '\n', '(', ')', /* singles */ + '&', '|', ';', /* doubles */ + 0 }; +#define xxreadtoken_singles 3 +#define xxreadtoken_doubles 3 + static const char xxreadtoken_tokens[] ALIGN1 = { TNL, TLP, TRP, /* only single occurrence allowed */ TBACKGND, TPIPE, TSEMI, /* if single occurrence */ @@ -11108,11 +11560,6 @@ static const char xxreadtoken_tokens[] ALIGN1 = { TAND, TOR, TENDCASE /* if double occurrence */ }; -#define xxreadtoken_doubles \ - (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) -#define xxreadtoken_singles \ - (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) - static int xxreadtoken(void) { @@ -11125,58 +11572,59 @@ xxreadtoken(void) if (needprompt) { setprompt(2); } - startlinno = plinno; + startlinno = g_parsefile->linno; for (;;) { /* until token or start of word found */ - c = pgetc_macro(); + c = pgetc_fast(); + if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA)) + continue; - if ((c != ' ') && (c != '\t') -#if ENABLE_ASH_ALIAS - && (c != PEOA) -#endif - ) { - if (c == '#') { - while ((c = pgetc()) != '\n' && c != PEOF) - continue; + if (c == '#') { + while ((c = pgetc()) != '\n' && c != PEOF) + continue; + pungetc(); + } else if (c == '\\') { + if (pgetc() != '\n') { pungetc(); - } else if (c == '\\') { - if (pgetc() != '\n') { - pungetc(); - goto READTOKEN1; - } - startlinno = ++plinno; - if (doprompt) - setprompt(2); - } else { - const char *p - = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; + break; /* return readtoken1(...) */ + } + startlinno = ++g_parsefile->linno; + if (doprompt) + setprompt(2); + } else { + const char *p; - if (c != PEOF) { - if (c == '\n') { - plinno++; - needprompt = doprompt; - } + p = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; + if (c != PEOF) { + if (c == '\n') { + g_parsefile->linno++; + needprompt = doprompt; + } - p = strchr(xxreadtoken_chars, c); - if (p == NULL) { - READTOKEN1: - return readtoken1(c, BASESYNTAX, (char *) NULL, 0); - } + p = strchr(xxreadtoken_chars, c); + if (p == NULL) + break; /* return readtoken1(...) */ - if ((size_t)(p - xxreadtoken_chars) >= xxreadtoken_singles) { - if (pgetc() == *p) { /* double occurrence? */ - p += xxreadtoken_doubles + 1; - } else { - pungetc(); - } + if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) { + int cc = pgetc(); + if (cc == c) { /* double occurrence? */ + p += xxreadtoken_doubles + 1; + } else { + pungetc(); +#if ENABLE_ASH_BASH_COMPAT + if (c == '&' && cc == '>') /* &> */ + break; /* return readtoken1(...) */ +#endif } } - lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; - return lasttoken; } + lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; + return lasttoken; } - } /* for */ + } /* for (;;) */ + + return readtoken1(c, BASESYNTAX, (char *) NULL, 0); } -#else +#else /* old xxreadtoken */ #define RETURN(token) return lasttoken = token static int xxreadtoken(void) @@ -11190,9 +11638,9 @@ xxreadtoken(void) if (needprompt) { setprompt(2); } - startlinno = plinno; + startlinno = g_parsefile->linno; for (;;) { /* until token or start of word found */ - c = pgetc_macro(); + c = pgetc_fast(); switch (c) { case ' ': case '\t': #if ENABLE_ASH_ALIAS @@ -11206,7 +11654,7 @@ xxreadtoken(void) continue; case '\\': if (pgetc() == '\n') { - startlinno = ++plinno; + startlinno = ++g_parsefile->linno; if (doprompt) setprompt(2); continue; @@ -11214,7 +11662,7 @@ xxreadtoken(void) pungetc(); goto breakloop; case '\n': - plinno++; + g_parsefile->linno++; needprompt = doprompt; RETURN(TNL); case PEOF: @@ -11246,7 +11694,7 @@ xxreadtoken(void) return readtoken1(c, BASESYNTAX, (char *)NULL, 0); #undef RETURN } -#endif /* NEW_xxreadtoken */ +#endif /* old xxreadtoken */ static int readtoken(void) @@ -11324,8 +11772,8 @@ peektoken(void) } /* - * Read and parse a command. Returns NEOF on end of file. (NULL is a - * valid parse tree indicating a blank line.) + * Read and parse a command. Returns NODE_EOF on end of file. + * (NULL is a valid parse tree indicating a blank line.) */ static union node * parsecmd(int interact) @@ -11339,7 +11787,7 @@ parsecmd(int interact) needprompt = 0; t = readtoken(); if (t == TEOF) - return NEOF; + return NODE_EOF; if (t == TNL) return NULL; tokpushback = 1; @@ -11384,7 +11832,8 @@ expandstr(const char *ps) { union node n; - /* XXX Fix (char *) cast. */ + /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value, + * and token processing _can_ alter it (delete NULs etc). */ setinputstring((char *)ps); readtoken1(pgetc(), PSSYNTAX, nullstr, 0); popfile(); @@ -11413,7 +11862,7 @@ evalstring(char *s, int mask) setstackmark(&smark); skip = 0; - while ((n = parsecmd(0)) != NEOF) { + while ((n = parsecmd(0)) != NODE_EOF) { evaltree(n, 0); popstackmark(&smark); skip = evalskip; @@ -11430,7 +11879,7 @@ evalstring(char *s, int mask) /* * The eval command. */ -static int +static int FAST_FUNC evalcmd(int argc UNUSED_PARAM, char **argv) { char *p; @@ -11486,8 +11935,11 @@ cmdloop(int top) #endif } n = parsecmd(inter); - /* showtree(n); DEBUG */ - if (n == NEOF) { +#if DEBUG + if (DEBUG > 2 && debug && (n != NODE_EOF)) + showtree(n); +#endif + if (n == NODE_EOF) { if (!top || numeof >= 50) break; if (!stoppedjobs()) { @@ -11528,7 +11980,16 @@ find_dot_file(char *name) if (strchr(name, '/')) return name; - while ((fullname = padvance(&path, name)) != NULL) { + /* IIRC standards do not say whether . is to be searched. + * And it is even smaller this way, making it unconditional for now: + */ + if (1) { /* ENABLE_ASH_BASH_COMPAT */ + fullname = name; + goto try_cur_dir; + } + + while ((fullname = path_advance(&path, name)) != NULL) { + try_cur_dir: if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { /* * Don't bother freeing here, since it will @@ -11536,7 +11997,8 @@ find_dot_file(char *name) */ return fullname; } - stunalloc(fullname); + if (fullname != name) + stunalloc(fullname); } /* not found in the PATH */ @@ -11544,7 +12006,7 @@ find_dot_file(char *name) /* NOTREACHED */ } -static int +static int FAST_FUNC dotcmd(int argc, char **argv) { struct strlist *sp; @@ -11579,7 +12041,7 @@ dotcmd(int argc, char **argv) return status; } -static int +static int FAST_FUNC exitcmd(int argc UNUSED_PARAM, char **argv) { if (stoppedjobs()) @@ -11710,7 +12172,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) e = ENOENT; idx = -1; loop: - while ((fullname = padvance(&path, name)) != NULL) { + while ((fullname = path_advance(&path, name)) != NULL) { stunalloc(fullname); /* NB: code below will still use fullname * despite it being "unallocated" */ @@ -11803,7 +12265,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) /* * The trap builtin. */ -static int +static int FAST_FUNC trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { char *action; @@ -11814,19 +12276,32 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) ap = argptr; if (!*ap) { for (signo = 0; signo < NSIG; signo++) { - if (trap[signo] != NULL) { - const char *sn; - - sn = get_signame(signo); + char *tr = trap_ptr[signo]; + if (tr) { + /* note: bash adds "SIG", but only if invoked + * as "bash". If called as "sh", or if set -o posix, + * then it prints short signal names. + * We are printing short names: */ out1fmt("trap -- %s %s\n", - single_quote(trap[signo]), sn); + single_quote(tr), + get_signame(signo)); + /* trap_ptr != trap only if we are in special-cased `trap` code. + * In this case, we will exit very soon, no need to free(). */ + /* if (trap_ptr != trap && tp[0]) */ + /* free(tr); */ } } + /* + if (trap_ptr != trap) { + free(trap_ptr); + trap_ptr = trap; + } + */ return 0; } - if (!ap[1]) - action = NULL; - else + + action = NULL; + if (ap[1]) action = *ap++; while (*ap) { signo = get_signum(*ap); @@ -11856,13 +12331,15 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* * Lists available builtins */ -static int +static int FAST_FUNC helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { unsigned col; unsigned i; - out1fmt("\nBuilt-in commands:\n-------------------\n"); + out1fmt( + "Built-in commands:\n" + "------------------\n"); for (col = 0, i = 0; i < ARRAY_SIZE(builtintab); i++) { col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), builtintab[i].name + 1); @@ -11892,14 +12369,14 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* * The export and readonly commands. */ -static int +static int FAST_FUNC exportcmd(int argc UNUSED_PARAM, char **argv) { struct var *vp; char *name; const char *p; char **aptr; - int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + int flag = argv[0][0] == 'r' ? VREADONLY : VEXPORT; if (nextopt("p") != 'p') { aptr = argptr; @@ -11943,7 +12420,7 @@ unsetfunc(const char *name) * variable to allow a function to be unset when there is a readonly variable * with the same name. */ -static int +static int FAST_FUNC unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { char **ap; @@ -11981,7 +12458,7 @@ static const unsigned char timescmd_str[] ALIGN1 = { 0 }; -static int +static int FAST_FUNC timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { long clk_tck, s, t; @@ -12004,36 +12481,14 @@ timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return 0; } -#if ENABLE_ASH_MATH_SUPPORT -static arith_t -dash_arith(const char *s) -{ - arith_t result; - int errcode = 0; - - INT_OFF; - result = arith(s, &errcode); - if (errcode < 0) { - if (errcode == -3) - ash_msg_and_raise_error("exponent less than 0"); - if (errcode == -2) - ash_msg_and_raise_error("divide by zero"); - if (errcode == -5) - ash_msg_and_raise_error("expression recursion loop detected"); - raise_error_syntax(s); - } - INT_ON; - - return result; -} - +#if ENABLE_SH_MATH_SUPPORT /* - * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell. - * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. + * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell. + * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. * - * Copyright (C) 2003 Vladimir Oleynik + * Copyright (C) 2003 Vladimir Oleynik */ -static int +static int FAST_FUNC letcmd(int argc UNUSED_PARAM, char **argv) { arith_t i; @@ -12042,12 +12497,12 @@ letcmd(int argc UNUSED_PARAM, char **argv) if (!*argv) ash_msg_and_raise_error("expression expected"); do { - i = dash_arith(*argv); + i = ash_arith(*argv); } while (*++argv); return !i; } -#endif /* ASH_MATH_SUPPORT */ +#endif /* SH_MATH_SUPPORT */ /* ============ miscbltin.c @@ -12075,7 +12530,7 @@ typedef enum __rlimit_resource rlim_t; * -d DELIM End on DELIM char, not newline * -e Use line editing (tty only) */ -static int +static int FAST_FUNC readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { static const char *const arg_REPLY[] = { "REPLY", NULL }; @@ -12104,8 +12559,8 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) rflag = 0; prompt = NULL; while ((i = nextopt("p:u:r" - USE_ASH_READ_TIMEOUT("t:") - USE_ASH_READ_NCHARS("n:s") + IF_ASH_READ_TIMEOUT("t:") + IF_ASH_READ_NCHARS("n:s") )) != '\0') { switch (i) { case 'p': @@ -12194,7 +12649,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif status = 0; - startword = 1; + startword = 2; backslash = 0; #if ENABLE_ASH_READ_TIMEOUT if (timeout) /* NB: ensuring end_ms is nonzero */ @@ -12202,6 +12657,8 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif STARTSTACKSTR(p); do { + const char *is_ifs; + #if ENABLE_ASH_READ_TIMEOUT if (end_ms) { struct pollfd pfd[1]; @@ -12231,25 +12688,36 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) continue; } if (!rflag && c == '\\') { - backslash++; + backslash = 1; continue; } if (c == '\n') break; - if (startword && *ifs == ' ' && strchr(ifs, c)) { - continue; + /* $IFS splitting */ +/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ + is_ifs = strchr(ifs, c); + if (startword && is_ifs) { + if (isspace(c)) + continue; + /* it is a non-space ifs char */ + startword--; + if (startword == 1) /* first one? */ + continue; /* yes, it is not next word yet */ } startword = 0; - if (ap[1] != NULL && strchr(ifs, c) != NULL) { + if (ap[1] != NULL && is_ifs) { + const char *beg; STACKSTRNUL(p); - setvar(*ap, stackblock(), 0); + beg = stackblock(); + setvar(*ap, beg, 0); ap++; - startword = 1; + /* can we skip one non-space ifs char? (2: yes) */ + startword = isspace(c) ? 2 : 1; STARTSTACKSTR(p); - } else { - put: - STPUTC(c, p); + continue; } + put: + STPUTC(c, p); } /* end of do {} while: */ #if ENABLE_ASH_READ_NCHARS @@ -12263,8 +12731,8 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif STACKSTRNUL(p); - /* Remove trailing blanks */ - while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL) + /* Remove trailing space ifs chars */ + while ((char *)stackblock() <= --p && isspace(*p) && strchr(ifs, *p) != NULL) *p = '\0'; setvar(*ap, stackblock(), 0); while (*++ap != NULL) @@ -12272,7 +12740,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return status; } -static int +static int FAST_FUNC umaskcmd(int argc UNUSED_PARAM, char **argv) { static const char permuser[3] ALIGN1 = "ugo"; @@ -12283,6 +12751,8 @@ umaskcmd(int argc UNUSED_PARAM, char **argv) S_IROTH, S_IWOTH, S_IXOTH }; + /* TODO: use bb_parse_mode() instead */ + char *ap; mode_t mask; int i; @@ -12325,7 +12795,7 @@ umaskcmd(int argc UNUSED_PARAM, char **argv) mask = 0; do { if (*ap >= '8' || *ap < '0') - ash_msg_and_raise_error(illnum, argv[1]); + ash_msg_and_raise_error(msg_illnum, argv[1]); mask = (mask << 3) + (*ap - '0'); } while (*++ap != '\0'); umask(mask); @@ -12349,7 +12819,6 @@ umaskcmd(int argc UNUSED_PARAM, char **argv) * * Public domain. */ - struct limits { uint8_t cmd; /* RLIMIT_xxx fit into it */ uint8_t factor_shift; /* shift by to get rlim_{cur,max} values */ @@ -12447,11 +12916,10 @@ printlim(enum limtype how, const struct rlimit *limit, } } -static int +static int FAST_FUNC ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { - int c; - rlim_t val = 0; + rlim_t val; enum limtype how = SOFT | HARD; const struct limits *l; int set, all = 0; @@ -12512,6 +12980,7 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) continue; set = *argptr ? 1 : 0; + val = 0; if (set) { char *p = *argptr; @@ -12520,15 +12989,13 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) if (strncmp(p, "unlimited\n", 9) == 0) val = RLIM_INFINITY; else { - val = (rlim_t) 0; - - while ((c = *p++) >= '0' && c <= '9') { - val = (val * 10) + (long)(c - '0'); - // val is actually 'unsigned long int' and can't get < 0 - if (val < (rlim_t) 0) - break; - } - if (c) + if (sizeof(val) == sizeof(int)) + val = bb_strtou(p, NULL, 10); + else if (sizeof(val) == sizeof(long)) + val = bb_strtoul(p, NULL, 10); + else + val = bb_strtoull(p, NULL, 10); + if (errno) ash_msg_and_raise_error("bad number"); val <<= l->factor_shift; } @@ -12558,654 +13025,6 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return 0; } - -/* ============ Math support */ - -#if ENABLE_ASH_MATH_SUPPORT - -/* Copyright (c) 2001 Aaron Lehmann - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* This is my infix parser/evaluator. It is optimized for size, intended - * as a replacement for yacc-based parsers. However, it may well be faster - * than a comparable parser written in yacc. The supported operators are - * listed in #defines below. Parens, order of operations, and error handling - * are supported. This code is thread safe. The exact expression format should - * be that which POSIX specifies for shells. */ - -/* The code uses a simple two-stack algorithm. See - * http://www.onthenet.com.au/~grahamis/int2008/week02/lect02.html - * for a detailed explanation of the infix-to-postfix algorithm on which - * this is based (this code differs in that it applies operators immediately - * to the stack instead of adding them to a queue to end up with an - * expression). */ - -/* To use the routine, call it with an expression string and error return - * pointer */ - -/* - * Aug 24, 2001 Manuel Novoa III - * - * Reduced the generated code size by about 30% (i386) and fixed several bugs. - * - * 1) In arith_apply(): - * a) Cached values of *numptr and &(numptr[-1]). - * b) Removed redundant test for zero denominator. - * - * 2) In arith(): - * a) Eliminated redundant code for processing operator tokens by moving - * to a table-based implementation. Also folded handling of parens - * into the table. - * b) Combined all 3 loops which called arith_apply to reduce generated - * code size at the cost of speed. - * - * 3) The following expressions were treated as valid by the original code: - * 1() , 0! , 1 ( *3 ) . - * These bugs have been fixed by internally enclosing the expression in - * parens and then checking that all binary ops and right parens are - * preceded by a valid expression (NUM_TOKEN). - * - * Note: It may be desirable to replace Aaron's test for whitespace with - * ctype's isspace() if it is used by another busybox applet or if additional - * whitespace chars should be considered. Look below the "#include"s for a - * precompiler test. - */ - -/* - * Aug 26, 2001 Manuel Novoa III - * - * Return 0 for null expressions. Pointed out by Vladimir Oleynik. - * - * Merge in Aaron's comments previously posted to the busybox list, - * modified slightly to take account of my changes to the code. - * - */ - -/* - * (C) 2003 Vladimir Oleynik - * - * - allow access to variable, - * used recursive find value indirection (c=2*2; a="c"; $((a+=2)) produce 6) - * - realize assign syntax (VAR=expr, +=, *= etc) - * - realize exponentiation (** operator) - * - realize comma separated - expr, expr - * - realise ++expr --expr expr++ expr-- - * - realise expr ? expr : expr (but, second expr calculate always) - * - allow hexadecimal and octal numbers - * - was restored loses XOR operator - * - remove one goto label, added three ;-) - * - protect $((num num)) as true zero expr (Manuel`s error) - * - always use special isspace(), see comment from bash ;-) - */ - -#define arith_isspace(arithval) \ - (arithval == ' ' || arithval == '\n' || arithval == '\t') - -typedef unsigned char operator; - -/* An operator's token id is a bit of a bitfield. The lower 5 bits are the - * precedence, and 3 high bits are an ID unique across operators of that - * precedence. The ID portion is so that multiple operators can have the - * same precedence, ensuring that the leftmost one is evaluated first. - * Consider * and /. */ - -#define tok_decl(prec,id) (((id)<<5)|(prec)) -#define PREC(op) ((op) & 0x1F) - -#define TOK_LPAREN tok_decl(0,0) - -#define TOK_COMMA tok_decl(1,0) - -#define TOK_ASSIGN tok_decl(2,0) -#define TOK_AND_ASSIGN tok_decl(2,1) -#define TOK_OR_ASSIGN tok_decl(2,2) -#define TOK_XOR_ASSIGN tok_decl(2,3) -#define TOK_PLUS_ASSIGN tok_decl(2,4) -#define TOK_MINUS_ASSIGN tok_decl(2,5) -#define TOK_LSHIFT_ASSIGN tok_decl(2,6) -#define TOK_RSHIFT_ASSIGN tok_decl(2,7) - -#define TOK_MUL_ASSIGN tok_decl(3,0) -#define TOK_DIV_ASSIGN tok_decl(3,1) -#define TOK_REM_ASSIGN tok_decl(3,2) - -/* all assign is right associativity and precedence eq, but (7+3)<<5 > 256 */ -#define convert_prec_is_assing(prec) do { if (prec == 3) prec = 2; } while (0) - -/* conditional is right associativity too */ -#define TOK_CONDITIONAL tok_decl(4,0) -#define TOK_CONDITIONAL_SEP tok_decl(4,1) - -#define TOK_OR tok_decl(5,0) - -#define TOK_AND tok_decl(6,0) - -#define TOK_BOR tok_decl(7,0) - -#define TOK_BXOR tok_decl(8,0) - -#define TOK_BAND tok_decl(9,0) - -#define TOK_EQ tok_decl(10,0) -#define TOK_NE tok_decl(10,1) - -#define TOK_LT tok_decl(11,0) -#define TOK_GT tok_decl(11,1) -#define TOK_GE tok_decl(11,2) -#define TOK_LE tok_decl(11,3) - -#define TOK_LSHIFT tok_decl(12,0) -#define TOK_RSHIFT tok_decl(12,1) - -#define TOK_ADD tok_decl(13,0) -#define TOK_SUB tok_decl(13,1) - -#define TOK_MUL tok_decl(14,0) -#define TOK_DIV tok_decl(14,1) -#define TOK_REM tok_decl(14,2) - -/* exponent is right associativity */ -#define TOK_EXPONENT tok_decl(15,1) - -/* For now unary operators. */ -#define UNARYPREC 16 -#define TOK_BNOT tok_decl(UNARYPREC,0) -#define TOK_NOT tok_decl(UNARYPREC,1) - -#define TOK_UMINUS tok_decl(UNARYPREC+1,0) -#define TOK_UPLUS tok_decl(UNARYPREC+1,1) - -#define PREC_PRE (UNARYPREC+2) - -#define TOK_PRE_INC tok_decl(PREC_PRE, 0) -#define TOK_PRE_DEC tok_decl(PREC_PRE, 1) - -#define PREC_POST (UNARYPREC+3) - -#define TOK_POST_INC tok_decl(PREC_POST, 0) -#define TOK_POST_DEC tok_decl(PREC_POST, 1) - -#define SPEC_PREC (UNARYPREC+4) - -#define TOK_NUM tok_decl(SPEC_PREC, 0) -#define TOK_RPAREN tok_decl(SPEC_PREC, 1) - -#define NUMPTR (*numstackptr) - -static int -tok_have_assign(operator op) -{ - operator prec = PREC(op); - - convert_prec_is_assing(prec); - return (prec == PREC(TOK_ASSIGN) || - prec == PREC_PRE || prec == PREC_POST); -} - -static int -is_right_associativity(operator prec) -{ - return (prec == PREC(TOK_ASSIGN) || prec == PREC(TOK_EXPONENT) - || prec == PREC(TOK_CONDITIONAL)); -} - -typedef struct { - arith_t val; - arith_t contidional_second_val; - char contidional_second_val_initialized; - char *var; /* if NULL then is regular number, - else is variable name */ -} v_n_t; - -typedef struct chk_var_recursive_looped_t { - const char *var; - struct chk_var_recursive_looped_t *next; -} chk_var_recursive_looped_t; - -static chk_var_recursive_looped_t *prev_chk_var_recursive; - -static int -arith_lookup_val(v_n_t *t) -{ - if (t->var) { - const char * p = lookupvar(t->var); - - if (p) { - int errcode; - - /* recursive try as expression */ - chk_var_recursive_looped_t *cur; - chk_var_recursive_looped_t cur_save; - - for (cur = prev_chk_var_recursive; cur; cur = cur->next) { - if (strcmp(cur->var, t->var) == 0) { - /* expression recursion loop detected */ - return -5; - } - } - /* save current lookuped var name */ - cur = prev_chk_var_recursive; - cur_save.var = t->var; - cur_save.next = cur; - prev_chk_var_recursive = &cur_save; - - t->val = arith (p, &errcode); - /* restore previous ptr after recursiving */ - prev_chk_var_recursive = cur; - return errcode; - } - /* allow undefined var as 0 */ - t->val = 0; - } - return 0; -} - -/* "applying" a token means performing it on the top elements on the integer - * stack. For a unary operator it will only change the top element, but a - * binary operator will pop two arguments and push a result */ -static int -arith_apply(operator op, v_n_t *numstack, v_n_t **numstackptr) -{ - v_n_t *numptr_m1; - arith_t numptr_val, rez; - int ret_arith_lookup_val; - - /* There is no operator that can work without arguments */ - if (NUMPTR == numstack) goto err; - numptr_m1 = NUMPTR - 1; - - /* check operand is var with noninteger value */ - ret_arith_lookup_val = arith_lookup_val(numptr_m1); - if (ret_arith_lookup_val) - return ret_arith_lookup_val; - - rez = numptr_m1->val; - if (op == TOK_UMINUS) - rez *= -1; - else if (op == TOK_NOT) - rez = !rez; - else if (op == TOK_BNOT) - rez = ~rez; - else if (op == TOK_POST_INC || op == TOK_PRE_INC) - rez++; - else if (op == TOK_POST_DEC || op == TOK_PRE_DEC) - rez--; - else if (op != TOK_UPLUS) { - /* Binary operators */ - - /* check and binary operators need two arguments */ - if (numptr_m1 == numstack) goto err; - - /* ... and they pop one */ - --NUMPTR; - numptr_val = rez; - if (op == TOK_CONDITIONAL) { - if (!numptr_m1->contidional_second_val_initialized) { - /* protect $((expr1 ? expr2)) without ": expr" */ - goto err; - } - rez = numptr_m1->contidional_second_val; - } else if (numptr_m1->contidional_second_val_initialized) { - /* protect $((expr1 : expr2)) without "expr ? " */ - goto err; - } - numptr_m1 = NUMPTR - 1; - if (op != TOK_ASSIGN) { - /* check operand is var with noninteger value for not '=' */ - ret_arith_lookup_val = arith_lookup_val(numptr_m1); - if (ret_arith_lookup_val) - return ret_arith_lookup_val; - } - if (op == TOK_CONDITIONAL) { - numptr_m1->contidional_second_val = rez; - } - rez = numptr_m1->val; - if (op == TOK_BOR || op == TOK_OR_ASSIGN) - rez |= numptr_val; - else if (op == TOK_OR) - rez = numptr_val || rez; - else if (op == TOK_BAND || op == TOK_AND_ASSIGN) - rez &= numptr_val; - else if (op == TOK_BXOR || op == TOK_XOR_ASSIGN) - rez ^= numptr_val; - else if (op == TOK_AND) - rez = rez && numptr_val; - else if (op == TOK_EQ) - rez = (rez == numptr_val); - else if (op == TOK_NE) - rez = (rez != numptr_val); - else if (op == TOK_GE) - rez = (rez >= numptr_val); - else if (op == TOK_RSHIFT || op == TOK_RSHIFT_ASSIGN) - rez >>= numptr_val; - else if (op == TOK_LSHIFT || op == TOK_LSHIFT_ASSIGN) - rez <<= numptr_val; - else if (op == TOK_GT) - rez = (rez > numptr_val); - else if (op == TOK_LT) - rez = (rez < numptr_val); - else if (op == TOK_LE) - rez = (rez <= numptr_val); - else if (op == TOK_MUL || op == TOK_MUL_ASSIGN) - rez *= numptr_val; - else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN) - rez += numptr_val; - else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN) - rez -= numptr_val; - else if (op == TOK_ASSIGN || op == TOK_COMMA) - rez = numptr_val; - else if (op == TOK_CONDITIONAL_SEP) { - if (numptr_m1 == numstack) { - /* protect $((expr : expr)) without "expr ? " */ - goto err; - } - numptr_m1->contidional_second_val_initialized = op; - numptr_m1->contidional_second_val = numptr_val; - } else if (op == TOK_CONDITIONAL) { - rez = rez ? - numptr_val : numptr_m1->contidional_second_val; - } else if (op == TOK_EXPONENT) { - if (numptr_val < 0) - return -3; /* exponent less than 0 */ - else { - arith_t c = 1; - - if (numptr_val) - while (numptr_val--) - c *= rez; - rez = c; - } - } else if (numptr_val==0) /* zero divisor check */ - return -2; - else if (op == TOK_DIV || op == TOK_DIV_ASSIGN) - rez /= numptr_val; - else if (op == TOK_REM || op == TOK_REM_ASSIGN) - rez %= numptr_val; - } - if (tok_have_assign(op)) { - char buf[sizeof(arith_t_type)*3 + 2]; - - if (numptr_m1->var == NULL) { - /* Hmm, 1=2 ? */ - goto err; - } - /* save to shell variable */ -#if ENABLE_ASH_MATH_SUPPORT_64 - snprintf(buf, sizeof(buf), "%lld", (arith_t_type) rez); -#else - snprintf(buf, sizeof(buf), "%ld", (arith_t_type) rez); -#endif - setvar(numptr_m1->var, buf, 0); - /* after saving, make previous value for v++ or v-- */ - if (op == TOK_POST_INC) - rez--; - else if (op == TOK_POST_DEC) - rez++; - } - numptr_m1->val = rez; - /* protect geting var value, is number now */ - numptr_m1->var = NULL; - return 0; - err: - return -1; -} - -/* longest must be first */ -static const char op_tokens[] ALIGN1 = { - '<','<','=',0, TOK_LSHIFT_ASSIGN, - '>','>','=',0, TOK_RSHIFT_ASSIGN, - '<','<', 0, TOK_LSHIFT, - '>','>', 0, TOK_RSHIFT, - '|','|', 0, TOK_OR, - '&','&', 0, TOK_AND, - '!','=', 0, TOK_NE, - '<','=', 0, TOK_LE, - '>','=', 0, TOK_GE, - '=','=', 0, TOK_EQ, - '|','=', 0, TOK_OR_ASSIGN, - '&','=', 0, TOK_AND_ASSIGN, - '*','=', 0, TOK_MUL_ASSIGN, - '/','=', 0, TOK_DIV_ASSIGN, - '%','=', 0, TOK_REM_ASSIGN, - '+','=', 0, TOK_PLUS_ASSIGN, - '-','=', 0, TOK_MINUS_ASSIGN, - '-','-', 0, TOK_POST_DEC, - '^','=', 0, TOK_XOR_ASSIGN, - '+','+', 0, TOK_POST_INC, - '*','*', 0, TOK_EXPONENT, - '!', 0, TOK_NOT, - '<', 0, TOK_LT, - '>', 0, TOK_GT, - '=', 0, TOK_ASSIGN, - '|', 0, TOK_BOR, - '&', 0, TOK_BAND, - '*', 0, TOK_MUL, - '/', 0, TOK_DIV, - '%', 0, TOK_REM, - '+', 0, TOK_ADD, - '-', 0, TOK_SUB, - '^', 0, TOK_BXOR, - /* uniq */ - '~', 0, TOK_BNOT, - ',', 0, TOK_COMMA, - '?', 0, TOK_CONDITIONAL, - ':', 0, TOK_CONDITIONAL_SEP, - ')', 0, TOK_RPAREN, - '(', 0, TOK_LPAREN, - 0 -}; -/* ptr to ")" */ -#define endexpression (&op_tokens[sizeof(op_tokens)-7]) - -static arith_t -arith(const char *expr, int *perrcode) -{ - char arithval; /* Current character under analysis */ - operator lasttok, op; - operator prec; - operator *stack, *stackptr; - const char *p = endexpression; - int errcode; - v_n_t *numstack, *numstackptr; - unsigned datasizes = strlen(expr) + 2; - - /* Stack of integers */ - /* The proof that there can be no more than strlen(startbuf)/2+1 integers - * in any given correct or incorrect expression is left as an exercise to - * the reader. */ - numstackptr = numstack = alloca((datasizes / 2) * sizeof(numstack[0])); - /* Stack of operator tokens */ - stackptr = stack = alloca(datasizes * sizeof(stack[0])); - - *stackptr++ = lasttok = TOK_LPAREN; /* start off with a left paren */ - *perrcode = errcode = 0; - - while (1) { - arithval = *expr; - if (arithval == 0) { - if (p == endexpression) { - /* Null expression. */ - return 0; - } - - /* This is only reached after all tokens have been extracted from the - * input stream. If there are still tokens on the operator stack, they - * are to be applied in order. At the end, there should be a final - * result on the integer stack */ - - if (expr != endexpression + 1) { - /* If we haven't done so already, */ - /* append a closing right paren */ - expr = endexpression; - /* and let the loop process it. */ - continue; - } - /* At this point, we're done with the expression. */ - if (numstackptr != numstack+1) { - /* ... but if there isn't, it's bad */ - err: - *perrcode = -1; - return *perrcode; - } - if (numstack->var) { - /* expression is $((var)) only, lookup now */ - errcode = arith_lookup_val(numstack); - } - ret: - *perrcode = errcode; - return numstack->val; - } - - /* Continue processing the expression. */ - if (arith_isspace(arithval)) { - /* Skip whitespace */ - goto prologue; - } - p = endofname(expr); - if (p != expr) { - size_t var_name_size = (p-expr) + 1; /* trailing zero */ - - numstackptr->var = alloca(var_name_size); - safe_strncpy(numstackptr->var, expr, var_name_size); - expr = p; - num: - numstackptr->contidional_second_val_initialized = 0; - numstackptr++; - lasttok = TOK_NUM; - continue; - } - if (isdigit(arithval)) { - numstackptr->var = NULL; -#if ENABLE_ASH_MATH_SUPPORT_64 - numstackptr->val = strtoll(expr, (char **) &expr, 0); -#else - numstackptr->val = strtol(expr, (char **) &expr, 0); -#endif - goto num; - } - for (p = op_tokens; ; p++) { - const char *o; - - if (*p == 0) { - /* strange operator not found */ - goto err; - } - for (o = expr; *p && *o == *p; p++) - o++; - if (!*p) { - /* found */ - expr = o - 1; - break; - } - /* skip tail uncompared token */ - while (*p) - p++; - /* skip zero delim */ - p++; - } - op = p[1]; - - /* post grammar: a++ reduce to num */ - if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) - lasttok = TOK_NUM; - - /* Plus and minus are binary (not unary) _only_ if the last - * token was as number, or a right paren (which pretends to be - * a number, since it evaluates to one). Think about it. - * It makes sense. */ - if (lasttok != TOK_NUM) { - switch (op) { - case TOK_ADD: - op = TOK_UPLUS; - break; - case TOK_SUB: - op = TOK_UMINUS; - break; - case TOK_POST_INC: - op = TOK_PRE_INC; - break; - case TOK_POST_DEC: - op = TOK_PRE_DEC; - break; - } - } - /* We don't want a unary operator to cause recursive descent on the - * stack, because there can be many in a row and it could cause an - * operator to be evaluated before its argument is pushed onto the - * integer stack. */ - /* But for binary operators, "apply" everything on the operator - * stack until we find an operator with a lesser priority than the - * one we have just extracted. */ - /* Left paren is given the lowest priority so it will never be - * "applied" in this way. - * if associativity is right and priority eq, applied also skip - */ - prec = PREC(op); - if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { - /* not left paren or unary */ - if (lasttok != TOK_NUM) { - /* binary op must be preceded by a num */ - goto err; - } - while (stackptr != stack) { - if (op == TOK_RPAREN) { - /* The algorithm employed here is simple: while we don't - * hit an open paren nor the bottom of the stack, pop - * tokens and apply them */ - if (stackptr[-1] == TOK_LPAREN) { - --stackptr; - /* Any operator directly after a */ - lasttok = TOK_NUM; - /* close paren should consider itself binary */ - goto prologue; - } - } else { - operator prev_prec = PREC(stackptr[-1]); - - convert_prec_is_assing(prec); - convert_prec_is_assing(prev_prec); - if (prev_prec < prec) - break; - /* check right assoc */ - if (prev_prec == prec && is_right_associativity(prec)) - break; - } - errcode = arith_apply(*--stackptr, numstack, &numstackptr); - if (errcode) goto ret; - } - if (op == TOK_RPAREN) { - goto err; - } - } - - /* Push this operator to the stack and remember it. */ - *stackptr++ = lasttok = op; - prologue: - ++expr; - } /* while */ -} -#endif /* ASH_MATH_SUPPORT */ - - /* ============ main() and helpers */ /* @@ -13222,7 +13041,7 @@ exitshell(void) status = exitstatus; TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); if (setjmp(loc.loc)) { - if (exception == EXEXIT) + if (exception_type == EXEXIT) /* dash bug: it just does _exit(exitstatus) here * but we have to do setjobctl(0) first! * (bug is still not fixed in dash-0.5.3 - if you run dash @@ -13235,6 +13054,7 @@ exitshell(void) if (p) { trap[0] = NULL; evalstring(p, 0); + free(p); } flush_stdout_stderr(); out: @@ -13247,7 +13067,7 @@ static void init(void) { /* from input.c: */ - basepf.nextc = basepf.buf = basebuf; + basepf.next_to_pgetc = basepf.buf = basebuf; /* from trap.c: */ signal(SIGCHLD, SIG_DFL); @@ -13255,7 +13075,6 @@ init(void) /* from var.c: */ { char **envp; - char ppid[sizeof(int)*3 + 1]; const char *p; struct stat st1, st2; @@ -13266,8 +13085,7 @@ init(void) } } - snprintf(ppid, sizeof(ppid), "%u", (unsigned) getppid()); - setvar("PPID", ppid, 0); + setvar("PPID", utoa(getppid()), 0); p = lookupvar("PWD"); if (p) @@ -13368,13 +13186,14 @@ reset(void) evalskip = 0; loopnest = 0; /* from input.c: */ - parselleft = parsenleft = 0; /* clear input buffer */ + g_parsefile->left_in_buffer = 0; + g_parsefile->left_in_line = 0; /* clear input buffer */ popallfiles(); /* from parser.c: */ tokpushback = 0; checkkwd = 0; /* from redir.c: */ - clearredir(0); + clearredir(/*drop:*/ 0); } #if PROFILE @@ -13392,8 +13211,8 @@ extern int etext(); int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int ash_main(int argc UNUSED_PARAM, char **argv) { - char *shinit; - volatile int state; + const char *shinit; + volatile smallint state; struct jmploc jmploc; struct stackmark smark; @@ -13415,21 +13234,20 @@ int ash_main(int argc UNUSED_PARAM, char **argv) #endif state = 0; if (setjmp(jmploc.loc)) { - int e; - int s; + smallint e; + smallint s; reset(); - e = exception; + e = exception_type; if (e == EXERROR) exitstatus = 2; s = state; if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) exitshell(); - - if (e == EXINT) { + if (e == EXINT) outcslow('\n', stderr); - } + popstackmark(&smark); FORCE_INT_ON; /* enable interrupts */ if (s == 1) @@ -13443,16 +13261,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv) exception_handler = &jmploc; #if DEBUG opentrace(); - trace_puts("Shell args: "); + TRACE(("Shell args: ")); trace_puts_args(argv); #endif rootpid = getpid(); -#if ENABLE_ASH_RANDOM_SUPPORT - /* Can use monotonic_ns() for better randomness but for now it is - * not used anywhere else in busybox... so avoid bloat */ - random_galois_LFSR = random_LCG = rootpid + monotonic_us(); -#endif init(); setstackmark(&smark); procargs(argv); @@ -13471,7 +13284,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) } } #endif - if (argv[0] && argv[0][0] == '-') + if (/* argv[0] && */ argv[0][0] == '-') isloginsh = 1; if (isloginsh) { state = 1; @@ -13495,15 +13308,21 @@ int ash_main(int argc UNUSED_PARAM, char **argv) } state3: state = 4; - if (minusc) + if (minusc) { + /* evalstring pushes parsefile stack. + * 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->fd = -1; evalstring(minusc, 0); + } if (sflag || minusc == NULL) { -#if ENABLE_FEATURE_EDITING_SAVEHISTORY +#if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY if (iflag) { const char *hp = lookupvar("HISTFILE"); - - if (hp != NULL) + if (hp) line_input_state->hist_file = hp; } #endif @@ -13523,14 +13342,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) /* NOTREACHED */ } -#if DEBUG -const char *applet_name = "debug stuff usage"; -int main(int argc, char **argv) -{ - return ash_main(argc, argv); -} -#endif - /*- * Copyright (c) 1989, 1991, 1993, 1994